debug 1.0.0.alpha1 → 1.0.0.beta1
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/.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
|