debug 1.0.0.alpha1 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +20 -19
- data/README.md +353 -51
- data/Rakefile +21 -1
- data/debug.gemspec +6 -11
- data/exe/rdbg +32 -1
- data/ext/debug/debug.c +118 -0
- data/ext/debug/extconf.rb +2 -0
- data/ext/debug/iseq_collector.c +91 -0
- data/lib/debug.rb +1 -1
- data/lib/debug/breakpoint.rb +310 -36
- data/lib/debug/client.rb +4 -11
- data/lib/debug/config.rb +100 -7
- data/lib/debug/console.rb +89 -0
- data/lib/debug/open.rb +10 -0
- data/lib/debug/run.rb +2 -0
- data/lib/debug/server.rb +128 -21
- data/lib/debug/session.rb +605 -170
- data/lib/debug/source_repository.rb +27 -20
- data/lib/debug/thread_client.rb +294 -119
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +325 -0
- metadata +22 -42
- data/lib/debug/repl.rb +0 -69
- data/lib/debug/tcpserver.rb +0 -22
- data/lib/debug/unixserver.rb +0 -18
data/lib/debug/session.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module DEBUGGER__
|
2
|
+
# used in thread_client.c
|
3
|
+
FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
|
4
|
+
:has_return_value, :return_value, :show_line)
|
5
|
+
end
|
6
|
+
|
7
|
+
require_relative 'debug.so'
|
5
8
|
|
6
9
|
require_relative 'source_repository'
|
7
10
|
require_relative 'breakpoint'
|
8
11
|
require_relative 'thread_client'
|
12
|
+
require_relative 'config'
|
9
13
|
|
10
14
|
class RubyVM::InstructionSequence
|
11
15
|
def traceable_lines_norec lines
|
@@ -39,6 +43,10 @@ class RubyVM::InstructionSequence
|
|
39
43
|
def locals
|
40
44
|
self.to_a[10]
|
41
45
|
end
|
46
|
+
|
47
|
+
def last_line
|
48
|
+
self.to_a[4][:code_location][2]
|
49
|
+
end
|
42
50
|
end
|
43
51
|
|
44
52
|
module DEBUGGER__
|
@@ -46,12 +54,18 @@ module DEBUGGER__
|
|
46
54
|
def initialize ui
|
47
55
|
@ui = ui
|
48
56
|
@sr = SourceRepository.new
|
49
|
-
@
|
50
|
-
|
57
|
+
@bps = {} # bp.key => bp
|
58
|
+
# [file, line] => LineBreakpoint
|
59
|
+
# "Error" => CatchBreakpoint
|
60
|
+
# Method => MethodBreakpoint
|
61
|
+
# [:watch, expr] => WatchExprBreakpoint
|
62
|
+
# [:check, expr] => CheckBreakpoint
|
51
63
|
@th_clients = {} # {Thread => ThreadClient}
|
52
64
|
@q_evt = Queue.new
|
53
65
|
@displays = []
|
54
66
|
@tc = nil
|
67
|
+
@tc_id = 0
|
68
|
+
@initial_commands = []
|
55
69
|
|
56
70
|
@tp_load_script = TracePoint.new(:script_compiled){|tp|
|
57
71
|
ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
|
@@ -69,13 +83,53 @@ module DEBUGGER__
|
|
69
83
|
iseq, src = ev_args
|
70
84
|
on_load iseq, src
|
71
85
|
tc << :continue
|
86
|
+
when :thread_begin
|
87
|
+
th = ev_args.shift
|
88
|
+
on_thread_begin th
|
89
|
+
tc << :continue
|
72
90
|
when :suspend
|
91
|
+
case ev_args.first
|
92
|
+
when :breakpoint
|
93
|
+
bp, i = bp_index ev_args[1]
|
94
|
+
if bp
|
95
|
+
@ui.puts "\nStop by \##{i} #{bp}"
|
96
|
+
end
|
97
|
+
when :trap
|
98
|
+
@ui.puts ''
|
99
|
+
@ui.puts "\nStop by #{ev_args[1]}"
|
100
|
+
end
|
101
|
+
|
73
102
|
if @displays.empty?
|
74
103
|
wait_command_loop tc
|
75
104
|
else
|
76
105
|
tc << [:eval, :display, @displays]
|
77
106
|
end
|
78
107
|
when :result
|
108
|
+
case ev_args.first
|
109
|
+
when :watch
|
110
|
+
bp = ev_args[1]
|
111
|
+
@bps[bp.key] = bp
|
112
|
+
show_bps bp
|
113
|
+
when :try_display
|
114
|
+
failed_results = ev_args[1]
|
115
|
+
if failed_results.size > 0
|
116
|
+
i, msg = failed_results.last
|
117
|
+
if i+1 == @displays.size
|
118
|
+
@ui.puts "canceled: #{@displays.pop}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
when :method_breakpoint
|
122
|
+
bp = ev_args[1]
|
123
|
+
if bp
|
124
|
+
@bps[bp.key] = bp
|
125
|
+
show_bps bp
|
126
|
+
else
|
127
|
+
# can't make a bp
|
128
|
+
end
|
129
|
+
else
|
130
|
+
# ignore
|
131
|
+
end
|
132
|
+
|
79
133
|
wait_command_loop tc
|
80
134
|
end
|
81
135
|
end
|
@@ -85,6 +139,17 @@ module DEBUGGER__
|
|
85
139
|
@management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
|
86
140
|
|
87
141
|
setup_threads
|
142
|
+
|
143
|
+
@tp_thread_begin = TracePoint.new(:thread_begin){|tp|
|
144
|
+
ThreadClient.current.on_thread_begin Thread.current
|
145
|
+
}.enable
|
146
|
+
end
|
147
|
+
|
148
|
+
def add_initial_commands cmds
|
149
|
+
cmds.each{|c|
|
150
|
+
c.gsub('#.*', '').strip!
|
151
|
+
@initial_commands << c unless c.empty?
|
152
|
+
}
|
88
153
|
end
|
89
154
|
|
90
155
|
def source path
|
@@ -109,10 +174,17 @@ module DEBUGGER__
|
|
109
174
|
retry
|
110
175
|
end
|
111
176
|
end
|
177
|
+
ensure
|
178
|
+
@tc = nil
|
112
179
|
end
|
113
180
|
|
114
181
|
def wait_command
|
115
|
-
|
182
|
+
if @initial_commands.empty?
|
183
|
+
line = @ui.readline
|
184
|
+
else
|
185
|
+
line = @initial_commands.shift.strip
|
186
|
+
@ui.puts "(rdbg:init) #{line}"
|
187
|
+
end
|
116
188
|
|
117
189
|
if line.empty?
|
118
190
|
if @repl_prev_line
|
@@ -130,40 +202,83 @@ module DEBUGGER__
|
|
130
202
|
# p cmd: [cmd, *arg]
|
131
203
|
|
132
204
|
case cmd
|
205
|
+
### Control flow
|
133
206
|
|
134
|
-
#
|
207
|
+
# * `s[tep]`
|
208
|
+
# * Step in. Resume the program until next breakable point.
|
135
209
|
when 's', 'step'
|
136
210
|
@tc << [:step, :in]
|
211
|
+
|
212
|
+
# * `n[ext]`
|
213
|
+
# * Step over. Resume the program until next line.
|
137
214
|
when 'n', 'next'
|
138
215
|
@tc << [:step, :next]
|
216
|
+
|
217
|
+
# * `fin[ish]`
|
218
|
+
# * Finish this frame. Resume the program until the current frame is finished.
|
139
219
|
when 'fin', 'finish'
|
140
220
|
@tc << [:step, :finish]
|
221
|
+
|
222
|
+
# * `c[ontinue]`
|
223
|
+
# * Resume the program.
|
141
224
|
when 'c', 'continue'
|
142
225
|
@tc << :continue
|
143
|
-
|
226
|
+
|
227
|
+
# * `q[uit]` or exit or `Ctrl-D`
|
228
|
+
# * Finish debugger (with the debuggee process on non-remote debugging).
|
229
|
+
when 'q', 'quit', 'exit'
|
144
230
|
if ask 'Really quit?'
|
145
231
|
@ui.quit arg.to_i
|
146
232
|
@tc << :continue
|
147
233
|
else
|
148
234
|
return :retry
|
149
235
|
end
|
150
|
-
|
151
|
-
|
236
|
+
|
237
|
+
# * `kill` or `q[uit]!`
|
238
|
+
# * Stop the debuggee process.
|
239
|
+
when 'kill', 'quit!', 'q!'
|
240
|
+
if ask 'Really kill?'
|
152
241
|
exit! (arg || 1).to_i
|
153
242
|
else
|
154
243
|
return :retry
|
155
244
|
end
|
156
245
|
|
157
|
-
|
246
|
+
### Breakpoint
|
247
|
+
|
248
|
+
# * `b[reak]`
|
249
|
+
# * Show all breakpoints.
|
250
|
+
# * `b[reak] <line>`
|
251
|
+
# * Set breakpoint on `<line>` at the current frame's file.
|
252
|
+
# * `b[reak] <file>:<line>` or `<file> <line>`
|
253
|
+
# * Set breakpoint on `<file>:<line>`.
|
254
|
+
# * `b[reak] <class>#<name>`
|
255
|
+
# * Set breakpoint on the method `<class>#<name>`.
|
256
|
+
# * `b[reak] <expr>.<name>`
|
257
|
+
# * Set breakpoint on the method `<expr>.<name>`.
|
258
|
+
# * `b[reak] ... if <expr>`
|
259
|
+
# * break if `<expr>` is true at specified location.
|
260
|
+
# * `b[reak] if <expr>`
|
261
|
+
# * break if `<expr>` is true at any lines.
|
262
|
+
# * Note that this feature is super slow.
|
158
263
|
when 'b', 'break'
|
159
264
|
if arg == nil
|
160
265
|
show_bps
|
266
|
+
return :retry
|
161
267
|
else
|
162
|
-
bp = repl_add_breakpoint
|
163
|
-
|
268
|
+
case bp = repl_add_breakpoint(arg)
|
269
|
+
when :noretry
|
270
|
+
when nil
|
271
|
+
return :retry
|
272
|
+
else
|
273
|
+
show_bps bp
|
274
|
+
return :retry
|
275
|
+
end
|
164
276
|
end
|
165
|
-
|
277
|
+
|
278
|
+
# skip
|
166
279
|
when 'bv'
|
280
|
+
require 'json'
|
281
|
+
|
167
282
|
h = Hash.new{|h, k| h[k] = []}
|
168
283
|
@bps.each{|key, bp|
|
169
284
|
if LineBreakpoint === bp
|
@@ -184,12 +299,33 @@ module DEBUGGER__
|
|
184
299
|
end
|
185
300
|
|
186
301
|
return :retry
|
302
|
+
|
303
|
+
# * `catch <Error>`
|
304
|
+
# * Set breakpoint on raising `<Error>`.
|
187
305
|
when 'catch'
|
188
306
|
if arg
|
189
307
|
bp = add_catch_breakpoint arg
|
190
308
|
show_bps bp if bp
|
309
|
+
else
|
310
|
+
show_bps
|
191
311
|
end
|
192
312
|
return :retry
|
313
|
+
|
314
|
+
# * `watch <expr>`
|
315
|
+
# * Stop the execution when the result of <expr> is changed.
|
316
|
+
# * Note that this feature is super slow.
|
317
|
+
when 'wat', 'watch'
|
318
|
+
if arg
|
319
|
+
@tc << [:eval, :watch, arg]
|
320
|
+
else
|
321
|
+
show_bps
|
322
|
+
return :retry
|
323
|
+
end
|
324
|
+
|
325
|
+
# * `del[ete]`
|
326
|
+
# * delete all breakpoints.
|
327
|
+
# * `del[ete] <bpnum>`
|
328
|
+
# * delete specified breakpoint.
|
193
329
|
when 'del', 'delete'
|
194
330
|
bp =
|
195
331
|
case arg
|
@@ -206,42 +342,84 @@ module DEBUGGER__
|
|
206
342
|
@ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
|
207
343
|
return :retry
|
208
344
|
|
209
|
-
|
210
|
-
when 'p'
|
211
|
-
@tc << [:eval, :p, arg.to_s]
|
212
|
-
when 'pp'
|
213
|
-
@tc << [:eval, :pp, arg.to_s]
|
214
|
-
when 'e', 'eval', 'call'
|
215
|
-
@tc << [:eval, :call, arg]
|
216
|
-
when 'irb'
|
217
|
-
@tc << [:eval, :call, 'binding.irb']
|
345
|
+
### Information
|
218
346
|
|
219
|
-
#
|
220
|
-
|
221
|
-
@tc << [:frame, :up]
|
222
|
-
when 'down'
|
223
|
-
@tc << [:frame, :down]
|
224
|
-
when 'frame', 'f'
|
225
|
-
@tc << [:frame, :set, arg]
|
226
|
-
|
227
|
-
# information
|
347
|
+
# * `bt` or `backtrace`
|
348
|
+
# * Show backtrace (frame) information.
|
228
349
|
when 'bt', 'backtrace'
|
229
350
|
@tc << [:show, :backtrace]
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
351
|
+
|
352
|
+
# * `l[ist]`
|
353
|
+
# * Show current frame's source code.
|
354
|
+
# * Next `list` command shows the successor lines.
|
355
|
+
# * `l[ist] -`
|
356
|
+
# * Show predecessor lines as opposed to the `list` command.
|
357
|
+
# * `l[ist] <start>` or `l[ist] <start>-<end>`
|
358
|
+
# * Show current frame's source code from the line <start> to <end> if given.
|
359
|
+
when 'l', 'list'
|
360
|
+
case arg ? arg.strip : nil
|
361
|
+
when /\A(\d+)\z/
|
362
|
+
@tc << [:show, :list, {start_line: arg.to_i - 1}]
|
363
|
+
when /\A-\z/
|
364
|
+
@tc << [:show, :list, {dir: -1}]
|
365
|
+
when /\A(\d+)-(\d+)\z/
|
366
|
+
@tc << [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
|
367
|
+
when nil
|
368
|
+
@tc << [:show, :list]
|
238
369
|
else
|
239
|
-
@ui.puts "
|
370
|
+
@ui.puts "Can not handle list argument: #{arg}"
|
240
371
|
return :retry
|
241
372
|
end
|
373
|
+
|
374
|
+
# * `edit`
|
375
|
+
# * Open the current file on the editor (use `EDITOR` environment variable).
|
376
|
+
# * Note that editted file will not be reloaded.
|
377
|
+
# * `edit <file>`
|
378
|
+
# * Open <file> on the editor.
|
379
|
+
when 'edit'
|
380
|
+
if @ui.remote?
|
381
|
+
@ui.puts "not supported on the remote console."
|
382
|
+
return :retry
|
383
|
+
end
|
384
|
+
|
385
|
+
begin
|
386
|
+
arg = resolve_path(arg) if arg
|
387
|
+
rescue Errno::ENOENT
|
388
|
+
@ui.puts "not found: #{arg}"
|
389
|
+
return :retry
|
390
|
+
end
|
391
|
+
|
392
|
+
@tc << [:show, :edit, arg]
|
393
|
+
|
394
|
+
# * `i[nfo]`
|
395
|
+
# * Show information about the current frame (local variables)
|
396
|
+
# * It includes `self` as `%self` and a return value as `%return`.
|
397
|
+
# * `i[nfo] <expr>`
|
398
|
+
# * Show information about the result of <expr>.
|
399
|
+
when 'i', 'info'
|
400
|
+
case arg
|
401
|
+
when nil
|
402
|
+
@tc << [:show, :local]
|
403
|
+
else
|
404
|
+
@tc << [:show, :object_info, arg]
|
405
|
+
end
|
406
|
+
|
407
|
+
# * `display`
|
408
|
+
# * Show display setting.
|
409
|
+
# * `display <expr>`
|
410
|
+
# * Show the result of `<expr>` at every suspended timing.
|
242
411
|
when 'display'
|
243
|
-
|
244
|
-
|
412
|
+
if arg && !arg.empty?
|
413
|
+
@displays << arg
|
414
|
+
@tc << [:eval, :try_display, @displays]
|
415
|
+
else
|
416
|
+
@tc << [:eval, :display, @displays]
|
417
|
+
end
|
418
|
+
|
419
|
+
# * `undisplay`
|
420
|
+
# * Remove all display settings.
|
421
|
+
# * `undisplay <displaynum>`
|
422
|
+
# * Remove a specified display setting.
|
245
423
|
when 'undisplay'
|
246
424
|
case arg
|
247
425
|
when /(\d+)/
|
@@ -258,12 +436,14 @@ module DEBUGGER__
|
|
258
436
|
end
|
259
437
|
return :retry
|
260
438
|
|
261
|
-
# trace
|
439
|
+
# * `trace [on|off]`
|
440
|
+
# * enable or disable line tracer.
|
262
441
|
when 'trace'
|
263
442
|
case arg
|
264
443
|
when 'on'
|
444
|
+
dir = __dir__
|
265
445
|
@tracer ||= TracePoint.new(){|tp|
|
266
|
-
next if tp.path ==
|
446
|
+
next if File.dirname(tp.path) == dir
|
267
447
|
next if tp.path == '<internal:trace_point>'
|
268
448
|
# next if tp.event != :line
|
269
449
|
@ui.puts pretty_tp(tp)
|
@@ -271,13 +451,62 @@ module DEBUGGER__
|
|
271
451
|
@tracer.enable
|
272
452
|
when 'off'
|
273
453
|
@tracer && @tracer.disable
|
274
|
-
else
|
275
|
-
enabled = (@tracer && @tracer.enabled?) ? true : false
|
276
|
-
@ui.puts "Trace #{enabled ? 'on' : 'off'}"
|
277
454
|
end
|
455
|
+
enabled = (@tracer && @tracer.enabled?) ? true : false
|
456
|
+
@ui.puts "Trace #{enabled ? 'on' : 'off'}"
|
278
457
|
return :retry
|
279
458
|
|
280
|
-
|
459
|
+
### Frame control
|
460
|
+
|
461
|
+
# * `f[rame]`
|
462
|
+
# * Show current frame.
|
463
|
+
# * `f[rame] <framenum>`
|
464
|
+
# * Specify frame. Evaluation are run on this frame environement.
|
465
|
+
when 'frame', 'f'
|
466
|
+
@tc << [:frame, :set, arg]
|
467
|
+
|
468
|
+
# * `up`
|
469
|
+
# * Specify upper frame.
|
470
|
+
when 'up'
|
471
|
+
@tc << [:frame, :up]
|
472
|
+
|
473
|
+
# * `down`
|
474
|
+
# * Specify down frame.
|
475
|
+
when 'down'
|
476
|
+
@tc << [:frame, :down]
|
477
|
+
|
478
|
+
### Evaluate
|
479
|
+
|
480
|
+
# * `p <expr>`
|
481
|
+
# * Evaluate like `p <expr>` on the current frame.
|
482
|
+
when 'p'
|
483
|
+
@tc << [:eval, :p, arg.to_s]
|
484
|
+
|
485
|
+
# * `pp <expr>`
|
486
|
+
# * Evaluate like `pp <expr>` on the current frame.
|
487
|
+
when 'pp'
|
488
|
+
@tc << [:eval, :pp, arg.to_s]
|
489
|
+
|
490
|
+
# * `e[val] <expr>`
|
491
|
+
# * Evaluate `<expr>` on the current frame.
|
492
|
+
when 'e', 'eval', 'call'
|
493
|
+
@tc << [:eval, :call, arg]
|
494
|
+
|
495
|
+
# * `irb`
|
496
|
+
# * Invoke `irb` on the current frame.
|
497
|
+
when 'irb'
|
498
|
+
if @ui.remote?
|
499
|
+
@ui.puts "not supported on the remote console."
|
500
|
+
return :retry
|
501
|
+
end
|
502
|
+
@tc << [:eval, :call, 'binding.irb']
|
503
|
+
|
504
|
+
### Thread control
|
505
|
+
|
506
|
+
# * `th[read]`
|
507
|
+
# * Show all threads.
|
508
|
+
# * `th[read] <thnum>`
|
509
|
+
# * Switch thread specified by `<thnum>`.
|
281
510
|
when 'th', 'thread'
|
282
511
|
case arg
|
283
512
|
when nil, 'list', 'l'
|
@@ -289,6 +518,21 @@ module DEBUGGER__
|
|
289
518
|
end
|
290
519
|
return :retry
|
291
520
|
|
521
|
+
### Help
|
522
|
+
|
523
|
+
# * `h[elp]`
|
524
|
+
# * Show help for all commands.
|
525
|
+
# * `h[elp] <command>`
|
526
|
+
# * Show help for the given command.
|
527
|
+
when 'h', 'help'
|
528
|
+
if arg
|
529
|
+
show_help arg
|
530
|
+
else
|
531
|
+
@ui.puts DEBUGGER__.help
|
532
|
+
end
|
533
|
+
return :retry
|
534
|
+
|
535
|
+
### END
|
292
536
|
else
|
293
537
|
@ui.puts "unknown command: #{line}"
|
294
538
|
@repl_prev_line = nil
|
@@ -305,6 +549,18 @@ module DEBUGGER__
|
|
305
549
|
return :retry
|
306
550
|
end
|
307
551
|
|
552
|
+
def show_help arg
|
553
|
+
DEBUGGER__.helps.each{|cat, cs|
|
554
|
+
cs.each{|ws, desc|
|
555
|
+
if ws.include? arg
|
556
|
+
@ui.puts desc
|
557
|
+
return
|
558
|
+
end
|
559
|
+
}
|
560
|
+
}
|
561
|
+
@ui.puts "not found: #{arg}"
|
562
|
+
end
|
563
|
+
|
308
564
|
def ask msg, default = 'Y'
|
309
565
|
opts = '[y/n]'.tr(default.downcase, default)
|
310
566
|
input = @ui.ask("#{msg} #{opts} ")
|
@@ -363,35 +619,84 @@ module DEBUGGER__
|
|
363
619
|
exit!
|
364
620
|
end
|
365
621
|
|
366
|
-
def
|
367
|
-
|
368
|
-
|
369
|
-
|
622
|
+
def iterate_bps
|
623
|
+
deleted_bps = []
|
624
|
+
i = 0
|
625
|
+
@bps.each{|key, bp|
|
626
|
+
if !bp.deleted?
|
627
|
+
yield key, bp, i
|
628
|
+
i += 1
|
629
|
+
else
|
630
|
+
deleted_bps << bp
|
370
631
|
end
|
371
632
|
}
|
633
|
+
ensure
|
634
|
+
deleted_bps.each{|bp| @bps.delete bp}
|
372
635
|
end
|
373
636
|
|
374
|
-
def
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
637
|
+
def show_bps specific_bp = nil
|
638
|
+
iterate_bps do |key, bp, i|
|
639
|
+
@ui.puts "#%d %s" % [i, bp.to_s] if !specific_bp || bp == specific_bp
|
640
|
+
end
|
641
|
+
end
|
379
642
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
643
|
+
def bp_index specific_bp_key
|
644
|
+
iterate_bps do |key, bp, i|
|
645
|
+
if key == specific_bp_key
|
646
|
+
return [bp, i]
|
647
|
+
end
|
385
648
|
end
|
649
|
+
nil
|
386
650
|
end
|
387
651
|
|
388
|
-
def
|
389
|
-
|
390
|
-
|
652
|
+
def delete_breakpoint arg = nil
|
653
|
+
case arg
|
654
|
+
when nil
|
655
|
+
@bps.each{|key, bp| bp.delete}
|
656
|
+
@bps.clear
|
657
|
+
else
|
658
|
+
del_bp = nil
|
659
|
+
iterate_bps{|key, bp, i| del_bp = bp if i == arg}
|
660
|
+
if del_bp
|
661
|
+
del_bp.delete
|
662
|
+
@bps.delete del_bp.key
|
663
|
+
return [arg, del_bp]
|
664
|
+
end
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def repl_add_breakpoint arg
|
669
|
+
arg.strip!
|
670
|
+
|
671
|
+
case arg
|
672
|
+
when /\Aif\s+(.+)\z/
|
673
|
+
cond = $1
|
674
|
+
when /(.+?)\s+if\s+(.+)\z/
|
675
|
+
sig = $1
|
676
|
+
cond = $2
|
677
|
+
else
|
678
|
+
sig = arg
|
679
|
+
end
|
680
|
+
|
681
|
+
case sig
|
682
|
+
when /\A(\d+)\z/
|
683
|
+
add_line_breakpoint @tc.location.path, $1.to_i, cond: cond
|
684
|
+
when /\A(.+)[:\s+](\d+)\z/
|
685
|
+
add_line_breakpoint $1, $2.to_i, cond: cond
|
686
|
+
when /\A(.+)([\.\#])(.+)\z/
|
687
|
+
@tc << [:breakpoint, :method, $1, $2, $3, cond]
|
688
|
+
return :noretry
|
689
|
+
when nil
|
690
|
+
add_check_breakpoint cond
|
691
|
+
else
|
692
|
+
@ui.puts "Unknown breakpoint format: #{arg}"
|
693
|
+
@ui.puts
|
694
|
+
show_help 'b'
|
391
695
|
end
|
392
|
-
thread_list
|
393
696
|
end
|
394
697
|
|
698
|
+
# threads
|
699
|
+
|
395
700
|
def update_thread_list
|
396
701
|
list = Thread.list
|
397
702
|
thcs = []
|
@@ -407,60 +712,64 @@ module DEBUGGER__
|
|
407
712
|
unmanaged << th
|
408
713
|
end
|
409
714
|
}
|
410
|
-
return thcs, unmanaged
|
715
|
+
return thcs.sort_by{|thc| thc.id}, unmanaged
|
411
716
|
end
|
412
717
|
|
413
|
-
def
|
414
|
-
|
415
|
-
|
416
|
-
@
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
718
|
+
def thread_list
|
719
|
+
thcs, unmanaged_ths = update_thread_list
|
720
|
+
thcs.each_with_index{|thc, i|
|
721
|
+
@ui.puts "#{@tc == thc ? "--> " : " "}\##{i} #{thc}"
|
722
|
+
}
|
723
|
+
|
724
|
+
if !unmanaged_ths.empty?
|
725
|
+
@ui.puts "The following threads are not managed yet by the debugger:"
|
726
|
+
unmanaged_ths.each{|th|
|
727
|
+
@ui.puts " " + th.to_s
|
728
|
+
}
|
424
729
|
end
|
425
730
|
end
|
426
731
|
|
427
|
-
def
|
428
|
-
|
429
|
-
|
430
|
-
if /(.+?)\s+if\s+(.+)\z/ =~ arg
|
431
|
-
sig = $1
|
432
|
-
cond = $2
|
433
|
-
else
|
434
|
-
sig = arg
|
435
|
-
end
|
732
|
+
def thread_switch n
|
733
|
+
thcs, unmanaged_ths = update_thread_list
|
436
734
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
add_method_breakpoint arg, cond
|
444
|
-
else
|
445
|
-
raise "unknown breakpoint format: #{arg}"
|
735
|
+
if tc = thcs[n]
|
736
|
+
if tc.mode
|
737
|
+
@tc = tc
|
738
|
+
else
|
739
|
+
@ui.puts "#{tc.thread} is not controllable yet."
|
740
|
+
end
|
446
741
|
end
|
742
|
+
thread_list
|
447
743
|
end
|
448
744
|
|
449
|
-
def
|
450
|
-
@
|
745
|
+
def thread_client_create th
|
746
|
+
@th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
|
451
747
|
end
|
452
748
|
|
453
749
|
def setup_threads
|
454
750
|
stop_all_threads do
|
455
751
|
Thread.list.each{|th|
|
456
|
-
|
752
|
+
thread_client_create(th)
|
457
753
|
}
|
458
754
|
end
|
459
755
|
end
|
460
756
|
|
757
|
+
def on_thread_begin th
|
758
|
+
if @th_clients.has_key? th
|
759
|
+
# OK
|
760
|
+
else
|
761
|
+
# TODO: NG?
|
762
|
+
thread_client_create th
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
461
766
|
def thread_client
|
462
767
|
thr = Thread.current
|
463
|
-
@th_clients
|
768
|
+
if @th_clients.has_key? thr
|
769
|
+
@th_clients[thr]
|
770
|
+
else
|
771
|
+
@th_clients[thr] = thread_client_create(thr)
|
772
|
+
end
|
464
773
|
end
|
465
774
|
|
466
775
|
def stop_all_threads
|
@@ -496,99 +805,120 @@ module DEBUGGER__
|
|
496
805
|
|
497
806
|
def on_load iseq, src
|
498
807
|
@sr.add iseq, src
|
499
|
-
|
500
|
-
|
501
|
-
|
808
|
+
|
809
|
+
pending_line_breakpoints do |bp|
|
810
|
+
if bp.path == (iseq.absolute_path || iseq.path)
|
811
|
+
bp.try_activate
|
502
812
|
end
|
813
|
+
end
|
814
|
+
end
|
815
|
+
|
816
|
+
# breakpoint management
|
817
|
+
|
818
|
+
def add_breakpoint bp
|
819
|
+
if @bps.has_key? bp.key
|
820
|
+
@ui.puts "duplicated breakpoint: #{bp}"
|
821
|
+
else
|
822
|
+
@bps[bp.key] = bp
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
def rehash_bps
|
827
|
+
bps = @bps.values
|
828
|
+
@bps.clear
|
829
|
+
bps.each{|bp|
|
830
|
+
add_breakpoint bp
|
503
831
|
}
|
504
832
|
end
|
505
833
|
|
506
|
-
|
834
|
+
def break? file, line
|
835
|
+
@bps.has_key? [file, line]
|
836
|
+
end
|
507
837
|
|
508
838
|
def add_catch_breakpoint arg
|
509
839
|
bp = CatchBreakpoint.new(arg)
|
510
|
-
|
511
|
-
bp
|
840
|
+
add_braekpoint bp
|
512
841
|
end
|
513
842
|
|
514
|
-
def
|
515
|
-
|
516
|
-
|
517
|
-
end
|
518
|
-
|
519
|
-
bp = case
|
520
|
-
when events.include?(:RUBY_EVENT_CALL)
|
521
|
-
# "def foo" line set bp on the beggining of method foo
|
522
|
-
LineBreakpoint.new(:call, iseq, line, cond)
|
523
|
-
when events.include?(:RUBY_EVENT_LINE)
|
524
|
-
LineBreakpoint.new(:line, iseq, line, cond)
|
525
|
-
when events.include?(:RUBY_EVENT_RETURN)
|
526
|
-
LineBreakpoint.new(:return, iseq, line, cond)
|
527
|
-
when events.include?(:RUBY_EVENT_B_RETURN)
|
528
|
-
LineBreakpoint.new(:b_return, iseq, line, cond)
|
529
|
-
when events.include?(:RUBY_EVENT_END)
|
530
|
-
LineBreakpoint.new(:end, iseq, line, cond)
|
531
|
-
else
|
532
|
-
nil
|
533
|
-
end
|
534
|
-
@bps[bp.key] = bp if bp
|
843
|
+
def add_check_breakpoint expr
|
844
|
+
bp = CheckBreakpoint.new(expr)
|
845
|
+
add_breakpoint bp
|
535
846
|
end
|
536
847
|
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
848
|
+
def resolve_path file
|
849
|
+
File.realpath(File.expand_path(file))
|
850
|
+
rescue Errno::ENOENT
|
851
|
+
return file if file == '-e'
|
852
|
+
raise
|
853
|
+
end
|
541
854
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
855
|
+
def add_line_breakpoint file, line, **kw
|
856
|
+
file = resolve_path(file)
|
857
|
+
bp = LineBreakpoint.new(file, line, **kw)
|
858
|
+
add_breakpoint bp
|
859
|
+
rescue Errno::ENOENT => e
|
860
|
+
@ui.puts e.message
|
861
|
+
end
|
546
862
|
|
547
|
-
|
548
|
-
|
549
|
-
|
863
|
+
def pending_line_breakpoints
|
864
|
+
@bps.find_all do |key, bp|
|
865
|
+
LineBreakpoint === bp && !bp.iseq
|
866
|
+
end.each do |key, bp|
|
867
|
+
yield bp
|
868
|
+
end
|
869
|
+
end
|
550
870
|
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
871
|
+
def method_added tp
|
872
|
+
b = tp.binding
|
873
|
+
if var_name = b.local_variables.first
|
874
|
+
mid = b.local_variable_get(var_name)
|
875
|
+
found = false
|
876
|
+
|
877
|
+
@bps.each{|k, bp|
|
878
|
+
case bp
|
879
|
+
when MethodBreakpoint
|
880
|
+
if bp.method.nil?
|
881
|
+
found = true
|
882
|
+
if bp.sig_method_name == mid.to_s
|
883
|
+
bp.enable(quiet: true)
|
559
884
|
end
|
560
885
|
end
|
561
886
|
end
|
887
|
+
}
|
888
|
+
unless found
|
889
|
+
METHOD_ADDED_TRACKER.disable
|
562
890
|
end
|
563
|
-
}
|
564
|
-
|
565
|
-
if nearest
|
566
|
-
add_line_breakpoint_exact nearest.iseq, nearest.events, file, nearest.line, cond
|
567
|
-
else
|
568
|
-
return nil
|
569
891
|
end
|
570
892
|
end
|
893
|
+
end
|
571
894
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
895
|
+
# String for requring location
|
896
|
+
# nil for -r
|
897
|
+
def self.require_location
|
898
|
+
locs = caller_locations
|
899
|
+
dir_prefix = /#{__dir__}/
|
577
900
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
901
|
+
locs.each do |loc|
|
902
|
+
case loc.absolute_path
|
903
|
+
when dir_prefix
|
904
|
+
when %r{rubygems/core_ext/kernel_require\.rb}
|
905
|
+
else
|
906
|
+
return loc
|
907
|
+
end
|
583
908
|
end
|
909
|
+
nil
|
910
|
+
end
|
584
911
|
|
585
|
-
|
586
|
-
|
587
|
-
|
912
|
+
def self.console
|
913
|
+
initialize_session UI_Console.new
|
914
|
+
|
915
|
+
@prev_handler = trap(:SIGINT){
|
916
|
+
ThreadClient.current.on_trap :SIGINT
|
917
|
+
}
|
588
918
|
end
|
589
919
|
|
590
|
-
def self.add_line_breakpoint file, line,
|
591
|
-
::DEBUGGER__::SESSION.add_line_breakpoint file, line,
|
920
|
+
def self.add_line_breakpoint file, line, **kw
|
921
|
+
::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
|
592
922
|
end
|
593
923
|
|
594
924
|
def self.add_catch_breakpoint pat
|
@@ -600,12 +930,117 @@ module DEBUGGER__
|
|
600
930
|
::DEBUGGER__.const_set(:SESSION, Session.new(ui))
|
601
931
|
|
602
932
|
# default breakpoints
|
603
|
-
|
933
|
+
|
934
|
+
# ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
|
604
935
|
|
605
936
|
Binding.module_eval do
|
606
937
|
::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1
|
607
938
|
def bp; nil; end
|
608
939
|
end
|
940
|
+
|
941
|
+
if ::DEBUGGER__::CONFIG[:nonstop] != '1'
|
942
|
+
if loc = ::DEBUGGER__.require_location
|
943
|
+
# require 'debug/console' or 'debug'
|
944
|
+
add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
|
945
|
+
else
|
946
|
+
# -r
|
947
|
+
add_line_breakpoint $0, 1, oneshot: true, hook_call: false
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
load_rc
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
def self.load_rc
|
956
|
+
['./rdbgrc.rb', File.expand_path('~/.rdbgrc.rb')].each{|path|
|
957
|
+
if File.file? path
|
958
|
+
load path
|
959
|
+
end
|
960
|
+
}
|
961
|
+
|
962
|
+
# debug commands file
|
963
|
+
[::DEBUGGER__::CONFIG[:init_script],
|
964
|
+
'./.rdbgrc',
|
965
|
+
File.expand_path('~/.rdbgrc')].each{|path|
|
966
|
+
next unless path
|
967
|
+
|
968
|
+
if File.file? path
|
969
|
+
::DEBUGGER__::SESSION.add_initial_commands File.readlines(path)
|
970
|
+
end
|
971
|
+
}
|
972
|
+
|
973
|
+
# given debug commands
|
974
|
+
if ::DEBUGGER__::CONFIG[:commands]
|
975
|
+
cmds = ::DEBUGGER__::CONFIG[:commands].split(';;')
|
976
|
+
::DEBUGGER__::SESSION.add_initial_commands cmds
|
977
|
+
end
|
978
|
+
end
|
979
|
+
|
980
|
+
def self.parse_help
|
981
|
+
helps = Hash.new{|h, k| h[k] = []}
|
982
|
+
desc = cat = nil
|
983
|
+
cmds = []
|
984
|
+
|
985
|
+
File.read(__FILE__).each_line do |line|
|
986
|
+
case line
|
987
|
+
when /\A\s*### (.+)/
|
988
|
+
cat = $1
|
989
|
+
break if $1 == 'END'
|
990
|
+
when /\A when (.+)/
|
991
|
+
next unless cat
|
992
|
+
next unless desc
|
993
|
+
ws = $1.split(/,\s*/).map{|e| e.gsub('\'', '')}
|
994
|
+
helps[cat] << [ws, desc]
|
995
|
+
desc = nil
|
996
|
+
cmds.concat ws
|
997
|
+
when /\A\s+# (\s*\*.+)/
|
998
|
+
if desc
|
999
|
+
desc << "\n" + $1
|
1000
|
+
else
|
1001
|
+
desc = $1
|
1002
|
+
end
|
1003
|
+
end
|
609
1004
|
end
|
1005
|
+
@commands = cmds
|
1006
|
+
@helps = helps
|
610
1007
|
end
|
1008
|
+
|
1009
|
+
def self.helps
|
1010
|
+
(defined?(@helps) && @helps) || parse_help
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
def self.commands
|
1014
|
+
(defined?(@commands) && @commands) || (parse_help; @commands)
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
def self.help
|
1018
|
+
r = []
|
1019
|
+
self.helps.each{|cat, cmds|
|
1020
|
+
r << "### #{cat}"
|
1021
|
+
r << ''
|
1022
|
+
cmds.each{|ws, desc|
|
1023
|
+
r << desc
|
1024
|
+
}
|
1025
|
+
r << ''
|
1026
|
+
}
|
1027
|
+
r.join("\n")
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
|
1031
|
+
|
1032
|
+
class ::Module
|
1033
|
+
def method_added mid; end
|
1034
|
+
def singleton_method_added mid; end
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
def self.method_added tp
|
1038
|
+
begin
|
1039
|
+
SESSION.method_added tp
|
1040
|
+
rescue Exception => e
|
1041
|
+
p e
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
METHOD_ADDED_TRACKER = self.create_method_added_tracker
|
611
1046
|
end
|