ruby-debug 0.1.5 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,627 @@
1
+ require 'ruby-debug/interface'
2
+
3
+ module Debugger
4
+ class CommandProcessor
5
+ DEBUG_LAST_CMD = []
6
+
7
+ attr_accessor :interface
8
+
9
+ def initialize(interface = LocalInterface.new)
10
+ @interface = interface
11
+ @display = []
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ def interface=(interface)
16
+ @mutex.synchronize do
17
+ @interface.close
18
+ @interface = interface
19
+ end
20
+ end
21
+
22
+ def at_breakpoint(context, breakpoint)
23
+ @mutex.synchronize do
24
+ return unless @interface
25
+ n = Debugger.breakpoints.index(breakpoint) + 1
26
+ print "Breakpoint %d at %s:%s\n", n, breakpoint.source, breakpoint.pos
27
+ end
28
+ rescue IOError
29
+ self.interface = nil
30
+ end
31
+
32
+ def at_catchpoint(context, excpt)
33
+ @mutex.synchronize do
34
+ frames = Debugger.current_context.frames
35
+ print "%s:%d: `%s' (%s)\n", frames[0].file, frames[0].line, excpt, excpt.class
36
+ fs = frames.size
37
+ tb = caller(0)[-fs..-1]
38
+ if tb
39
+ for i in tb
40
+ print "\tfrom %s\n", i
41
+ end
42
+ end
43
+ end
44
+ rescue IOError
45
+ self.interface = nil
46
+ end
47
+
48
+ def at_tracing(context, file, line)
49
+ @mutex.synchronize do
50
+ print "Tracing(%d):%s:%s %s", context.thnum, file, line, line_at(file, line)
51
+ end
52
+ rescue IOError
53
+ self.interface = nil
54
+ end
55
+
56
+ def at_line(*args)
57
+ @mutex.synchronize do
58
+ process_commands(*args)
59
+ end
60
+ rescue IOError
61
+ puts 'error'
62
+ self.interface = nil
63
+ end
64
+
65
+ private
66
+
67
+ def print(*args)
68
+ @interface.print(*args)
69
+ end
70
+
71
+ def process_commands(context, file, line, binding)
72
+ frame_pos = 0
73
+ binding_file = file
74
+ binding_line = line
75
+ previous_line = nil
76
+ print "%s:%d: %s", binding_file, binding_line, line_at(binding_file, binding_line)
77
+ display_expressions(binding)
78
+ prompt = true
79
+ while prompt and input = @interface.read_command("(rdb:%d) " % context.thnum)
80
+ catch(:debug_error) do
81
+ if input == ""
82
+ next unless DEBUG_LAST_CMD[0]
83
+ input = DEBUG_LAST_CMD[0]
84
+ else
85
+ DEBUG_LAST_CMD[0] = input
86
+ end
87
+
88
+ case input
89
+ when /^\s*s(?:tep)?(?:\s+(\d+))?$/
90
+ context.stop_next = $1 ? $1.to_i : 1
91
+ prompt = false
92
+
93
+ when /^\s*c(?:ont)?$|^\s*r(?:un)?$/
94
+ prompt = false
95
+
96
+ when /^\s*v(?:ar)?\s+/
97
+ debug_variable_info($', binding)
98
+
99
+ when /^\s*w(?:here)?$/, /^\s*f(?:rame)?$/
100
+ display_frames(context, frame_pos)
101
+
102
+ when /^\s*l(?:ist)?(?:\s+(.+))?$/
103
+ if not $1
104
+ b = previous_line ? previous_line + 10 : binding_line - 5
105
+ e = b + 9
106
+ elsif $1 == '-'
107
+ b = previous_line ? previous_line - 10 : binding_line - 5
108
+ e = b + 9
109
+ else
110
+ b, e = $1.split(/[-,]/)
111
+ if e
112
+ b = b.to_i
113
+ e = e.to_i
114
+ else
115
+ b = b.to_i - 5
116
+ e = b + 9
117
+ end
118
+ end
119
+ previous_line = b
120
+ display_list(b, e, binding_file, binding_line)
121
+
122
+ when /^\s*n(?:ext)?(?:\s+(\d+))?$/
123
+ steps = $1 ? $1.to_i : 1
124
+ context.step_over steps, context.frames.size - frame_pos
125
+ prompt = false
126
+
127
+ when /^\s*up(?:\s+(\d+))?$/
128
+ previous_line = nil
129
+ frame_pos += $1 ? $1.to_i : 1
130
+ if frame_pos >= context.frames.size
131
+ frame_pos = context.frames.size - 1
132
+ print "At toplevel"
133
+ end
134
+ frame = context.frames[frame_pos]
135
+ binding, binding_file, binding_line = frame.binding, frame.file, frame.line
136
+ print format_frame(frame, frame_pos)
137
+
138
+ when /^\s*down(?:\s+(\d+))?$/
139
+ previous_line = nil
140
+ frame_pos -= $1 ? $1.to_i : 1
141
+ if frame_pos < 0
142
+ frame_pos = 0
143
+ print "At stack bottom\n"
144
+ end
145
+ frame = context.frames[frame_pos]
146
+ binding, binding_file, binding_line = frame.binding, frame.file, frame.line
147
+ print format_frame(frame, frame_pos)
148
+
149
+ when /^\s*fin(?:ish)?$/
150
+ if frame_pos == context.frames.size
151
+ print "\"finish\" not meaningful in the outermost frame.\n"
152
+ else
153
+ context.stop_frame = context.frames.size - frame_pos
154
+ frame_pos = 0
155
+ prompt = false
156
+ end
157
+
158
+ when /^\s*b(?:reak)?\s+(?:(.+):)?([^.:\s]+)\s*(?:\sif\s+(.+))?$/
159
+ pos = $2
160
+ expr = $3
161
+ b_file = file
162
+ if $1
163
+ klass = debug_silent_eval($1, binding)
164
+ if klass && !klass.kind_of?(Module)
165
+ print "Unknown class #$1\n"
166
+ throw :debug_error
167
+ end
168
+ klass = klass.name if klass
169
+ b_file = $1
170
+ end
171
+ if pos =~ /^\d+$/
172
+ pname = pos
173
+ pos = pos.to_i
174
+ else
175
+ pname = pos = pos.intern.id2name
176
+ end
177
+ b_file = File.basename(b_file)
178
+ Debugger.add_breakpoint klass || b_file, pos, expr
179
+ print "Set breakpoint %d at %s:%s\n", Debugger.breakpoints.size, klass || b_file, pname
180
+
181
+ when /^\s*b(?:reak)?\s+(.+)[#.]([^.:\s]+)(?:\s+if\s+(.+))?$/
182
+ pos = $2.intern.id2name
183
+ expr = $3
184
+ klass = debug_eval($1, binding)
185
+ if klass.nil? || !klass.kind_of?(Module)
186
+ print "Unknown class #$1\n"
187
+ throw :debug_error
188
+ end
189
+ Debugger.add_breakpoint klass.name, pos, expr
190
+ print "Set breakpoint %d at %s.%s\n", Debugger.breakpoints.size, klass, pos
191
+
192
+ when /^\s*b(?:reak)?$/
193
+ unless Debugger.breakpoints.empty?
194
+ print "Breakpoints:\n"
195
+ Debugger.breakpoints.each_with_index do |b, n|
196
+ if b.expr.nil?
197
+ print " %d %s:%s\n", n+1, b.source, b.pos
198
+ else
199
+ print " %d %s:%s if %s\n", n+1, b.source, b.pos, b.expr
200
+ end
201
+ end
202
+ print "\n"
203
+ else
204
+ print "No breakpoints\n"
205
+ end
206
+ when /^\s*del(?:ete)?(?:\s+(\d+))?$/
207
+ pos = $1
208
+ unless pos
209
+ input = @interface.confirm("Clear all breakpoints? (y/n) ")
210
+ if input == "y"
211
+ Debugger.breakpoints.clear
212
+ end
213
+ else
214
+ pos = pos.to_i
215
+ unless Debugger.breakpoints.delete_at(pos-1)
216
+ print "Breakpoint %d is not defined\n", pos
217
+ end
218
+ end
219
+
220
+ when /^\s*th(?:read)?\s+/
221
+ if debug_thread_info($') == :cont
222
+ prompt = false
223
+ end
224
+
225
+ when /^\s*m(?:ethod)?\s+/
226
+ debug_method_info($', binding)
227
+
228
+ when /^\s*pp\s+/
229
+ out = StringIO.new
230
+ PP.pp(debug_eval($', binding), out) rescue out.puts $!.message
231
+ print out.string
232
+
233
+ when /^\s*(\s*p|e(?:val)?)\s+/
234
+ print "%s\n", debug_eval($', binding).inspect
235
+
236
+ when /^\s*h(?:elp)?(?:\s+(.+))?$/
237
+ debug_print_help($1)
238
+
239
+ when /^\s*q(?:uit)?$/
240
+ input = @interface.confirm("Really quit? (y/n) ")
241
+ if input == "y"
242
+ exit! # exit -> exit!: No graceful way to stop threads...
243
+ end
244
+
245
+ when /^\s*disp(?:lay)?\s+(.+)$/
246
+ exp = $1
247
+ @display.push [true, exp]
248
+ print "%d: ", @display.size
249
+ display_expression(exp, binding)
250
+
251
+ when /^\s*disp(?:lay)?$/
252
+ display_expressions(binding)
253
+
254
+ when /^\s*undisp(?:lay)?(?:\s+(\d+))?$/
255
+ pos = $1
256
+ unless pos
257
+ input = @interface.confirm("Clear all expressions? (y/n) ")
258
+ if input == "y"
259
+ for d in @display
260
+ d[0] = false
261
+ end
262
+ end
263
+ else
264
+ pos = pos.to_i
265
+ if @display[pos-1]
266
+ @display[pos-1][0] = false
267
+ else
268
+ print "Display expression %d is not defined\n", pos
269
+ end
270
+ end
271
+
272
+ when /^\s*cat(?:ch)?(?:\s+(.+))?$/
273
+ if $1
274
+ excn = $1
275
+ if excn == 'off'
276
+ Debugger.catchpoint = nil
277
+ print "Clear catchpoint.\n"
278
+ else
279
+ Debugger.catchpoint = excn
280
+ print "Set catchpoint %s.\n", excn
281
+ end
282
+ else
283
+ if Debugger.catchpoint
284
+ print "Catchpoint %s.\n", Debugger.catchpoint
285
+ else
286
+ print "No catchpoint.\n"
287
+ end
288
+ end
289
+
290
+ when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/
291
+ if defined?( $2 )
292
+ Debugger.tracing = $1 == 'on'
293
+ elsif defined?( $1 )
294
+ context.tracing = $1 == 'on'
295
+ end
296
+ if Debugger.tracing || context.tracing
297
+ print "Trace on.\n"
298
+ else
299
+ print "Trace off.\n"
300
+ end
301
+
302
+ else
303
+ print "Unknown command\n"
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ def display_expressions(binding)
310
+ n = 1
311
+ for d in @display
312
+ if d[0]
313
+ print "%d: ", n
314
+ display_expression(d[1], binding)
315
+ end
316
+ n += 1
317
+ end
318
+ end
319
+
320
+ def display_expression(exp, binding)
321
+ print "%s = %s\n", exp, debug_silent_eval(exp, binding).to_s
322
+ end
323
+
324
+ def debug_eval(str, binding)
325
+ begin
326
+ val = eval(str, binding)
327
+ rescue StandardError, ScriptError => e
328
+ at = eval("caller(1)", binding)
329
+ print "%s:%s\n", at.shift, e.to_s.sub(/\(eval\):1:(in `.*?':)?/, '')
330
+ for i in at
331
+ print "\tfrom %s\n", i
332
+ end
333
+ throw :debug_error
334
+ end
335
+ end
336
+
337
+ def debug_silent_eval(str, binding)
338
+ begin
339
+ eval(str, binding)
340
+ rescue StandardError, ScriptError
341
+ nil
342
+ end
343
+ end
344
+
345
+ def debug_variable_info(input, binding)
346
+ case input
347
+ when /^\s*g(?:lobal)?\s*$/
348
+ var_list(global_variables, binding)
349
+
350
+ when /^\s*l(?:ocal)?\s*$/
351
+ var_list(eval("local_variables", binding), binding)
352
+
353
+ when /^\s*i(?:nstance)?\s+/
354
+ obj = debug_eval($', binding)
355
+ var_list(obj.instance_variables, obj.instance_eval{binding()})
356
+
357
+ when /^\s*c(?:onst(?:ant)?)?\s+/
358
+ obj = debug_eval($', binding)
359
+ unless obj.kind_of? Module
360
+ print "Should be Class/Module: %s\n", $'
361
+ else
362
+ var_list(obj.constants, obj.module_eval{binding()})
363
+ end
364
+ end
365
+ end
366
+
367
+ def display_frames(context, pos)
368
+ context.frames.each_with_index do |frame, idx|
369
+ if idx == pos
370
+ print "--> "
371
+ else
372
+ print " "
373
+ end
374
+ print format_frame(frame, idx)
375
+ end
376
+ end
377
+
378
+ def format_frame(frame, pos)
379
+ file, line, id = frame.file, frame.line, frame.id
380
+ "#%d %s:%s%s\n" % [pos + 1, file, line, (id ? ":in `#{id.id2name}'" : "")]
381
+ end
382
+
383
+ def var_list(ary, binding)
384
+ ary.sort!
385
+ for v in ary
386
+ print " %s => %s\n", v, eval(v, binding).inspect
387
+ end
388
+ end
389
+
390
+ def display_list(b, e, file, line)
391
+ print "[%d, %d] in %s\n", b, e, file
392
+ if lines = SCRIPT_LINES__[file] and lines != true
393
+ n = 0
394
+ b.upto(e) do |n|
395
+ if n > 0 && lines[n-1]
396
+ if n == line
397
+ print "=> %d %s\n", n, lines[n-1].chomp
398
+ else
399
+ print " %d %s\n", n, lines[n-1].chomp
400
+ end
401
+ end
402
+ end
403
+ else
404
+ print "No sourcefile available for %s\n", file
405
+ end
406
+ end
407
+
408
+ def debug_method_info(input, binding)
409
+ case input
410
+ when /^i(:?nstance)?\s+/
411
+ obj = debug_eval($', binding)
412
+
413
+ len = 0
414
+ for v in obj.methods.sort
415
+ len += v.size + 1
416
+ if len > 70
417
+ len = v.size + 1
418
+ print "\n"
419
+ end
420
+ print "%s ", v
421
+ end
422
+ print "\n"
423
+
424
+ else
425
+ obj = debug_eval(input, binding)
426
+ unless obj.kind_of? Module
427
+ print "Should be Class/Module: %s\n", input
428
+ else
429
+ len = 0
430
+ for v in obj.instance_methods(false).sort
431
+ len += v.size + 1
432
+ if len > 70
433
+ len = v.size + 1
434
+ print "\n"
435
+ end
436
+ print "%s ", v
437
+ end
438
+ print "\n"
439
+ end
440
+ end
441
+ end
442
+
443
+ def display_context(c)
444
+ if c.thread == Thread.current
445
+ print "+"
446
+ else
447
+ print " "
448
+ end
449
+ print "%d ", c.thnum
450
+ print "%s\t", c.thread.inspect
451
+ last_frame = c.frames.first
452
+ if last_frame
453
+ print "%s:%d", last_frame.file, last_frame.line
454
+ end
455
+ print "\n"
456
+ end
457
+
458
+ def display_all_contexts
459
+ threads = Debugger.contexts.sort_by{|c| c.thnum}.each do |c|
460
+ display_context(c)
461
+ end
462
+ end
463
+
464
+ def get_context(thnum)
465
+ Debugger.contexts.find{|c| c.thnum == thnum}
466
+ end
467
+
468
+ def debug_thread_info(input)
469
+ case input
470
+ when /^l(?:ist)?/
471
+ display_all_contexts
472
+
473
+ when /^c(?:ur(?:rent)?)?$/
474
+ display_context(Debugger.current_context)
475
+
476
+ when /^(?:sw(?:itch)?\s+)?(\d+)/
477
+ c = get_context($1.to_i)
478
+ if c == Debugger.current_context
479
+ print "It's the current thread.\n"
480
+ else
481
+ display_context(c)
482
+ c.stop_next = 1
483
+ c.thread.run
484
+ return :cont
485
+ end
486
+
487
+ when /^stop\s+(\d+)/
488
+ c = get_context($1.to_i)
489
+ if c == Debugger.current_context
490
+ print "It's the current thread.\n"
491
+ elsif c.thread.stop?
492
+ print "Already stopped.\n"
493
+ else
494
+ display_context(c)
495
+ c.set_suspend
496
+ end
497
+
498
+ when /^resume\s+(\d+)/
499
+ c = get_context($1.to_i)
500
+ if c == Debugger.current_context
501
+ print "It's the current thread.\n"
502
+ elsif !c.thread.stop?
503
+ print "Already running."
504
+ else
505
+ display_context(c)
506
+ c.thread.run
507
+ end
508
+ end
509
+ end
510
+
511
+ def line_at(file, line)
512
+ lines = SCRIPT_LINES__[file]
513
+ if lines
514
+ return "\n" if lines == true
515
+ line = lines[line-1]
516
+ return "\n" unless line
517
+ return line.gsub(/^\s+/, '')
518
+ end
519
+ return "\n"
520
+ end
521
+
522
+ COMMANDS = {
523
+ 'break' => %{
524
+ b[reak]\tlist breakpoints
525
+ b[reak] [file|class(:|.)]<line|method> [if expr] -
526
+ set breakpoint to some position, (optionally) if expr == true
527
+ },
528
+ 'delete' => %{
529
+ del[ete][ nnn]\tdelete some or all breakpoints
530
+ },
531
+ 'catch' => %{
532
+ cat[ch]\t\t\tshow catchpoint
533
+ cat[ch] <an Exception>\tset catchpoint to an exception
534
+ },
535
+ 'display' => %{
536
+ disp[lay] <expression>\tadd expression into display expression list
537
+ },
538
+ 'undisplay' => %{
539
+ undisp[lay][ nnn]\tdelete one particular or all display expressions
540
+ },
541
+ 'cont' => %{
542
+ c[ont]\trun until program ends or hit breakpoint
543
+ },
544
+ 'run' => %{
545
+ r[un]\talias for cont
546
+ },
547
+ 'step' => %{
548
+ s[tep][ nnn]\tstep (into methods) one line or till line nnn
549
+ },
550
+ 'next' => %{
551
+ n[ext][ nnn]\tgo over one line or till line nnn
552
+ },
553
+ 'where' => %{
554
+ w[here]\tdisplay frames
555
+ },
556
+ 'frame' => %{
557
+ f[rame]\talias for where
558
+ },
559
+ 'list' => %{
560
+ l[ist][ (-|nn-mm)]\tlist program, '-' list backwards, nn-mm list given lines
561
+ },
562
+ 'up' => %{
563
+ up[ nn]\tmove to higher frame
564
+ },
565
+ 'down' => %{
566
+ down[ nn]\tmove to lower frame
567
+ },
568
+ 'finish' => %{
569
+ fin[ish]\treturn to outer frame
570
+ },
571
+ 'quit' => %{
572
+ q[uit]\texit from debugger
573
+ },
574
+ 'trace' => %{
575
+ tr[ace] (on|off)\tset trace mode of current thread
576
+ tr[ace] (on|off) all\tset trace mode of all threads
577
+ },
578
+ 'var' => %{
579
+ v[ar] g[lobal]\t\t\tshow global variables
580
+ v[ar] l[ocal]\t\t\tshow local variables
581
+ v[ar] i[nstance] <object>\tshow instance variables of object
582
+ v[ar] c[onst] <object>\t\tshow constants of object
583
+ },
584
+ 'method' => %{
585
+ m[ethod] i[nstance] <obj>\tshow methods of object
586
+ m[ethod] <class|module>\t\tshow instance methods of class or module
587
+ },
588
+ 'thread' => %{
589
+ th[read] l[ist]\t\t\tlist all threads
590
+ th[read] c[ur[rent]]\t\tshow current thread
591
+ th[read] [sw[itch]] <nnn>\tswitch thread context to nnn
592
+ th[read] stop <nnn>\t\tstop thread nnn
593
+ th[read] resume <nnn>\t\tresume thread nnn
594
+ },
595
+ 'p' => %{
596
+ p expression\tevaluate expression and print its value
597
+ },
598
+ 'eval' => %{
599
+ e[val] expression\tevaluate expression and print its value,
600
+ \t\t\talias for p
601
+ },
602
+ 'pp' => %{
603
+ pp expression\tevaluate expression and print its value
604
+ },
605
+ 'help' => %{
606
+ h[elp]\tprint this help
607
+ }
608
+ }
609
+
610
+ def debug_print_help(command)
611
+ print "ruby-debug help v.#{Debugger::VERSION}\n"
612
+ help = COMMANDS[command]
613
+ if help
614
+ print help.split("\n").map{|l| l.gsub(/^ +/, '')}.join("\n")
615
+ else
616
+ print "Available commands:\n"
617
+ require 'enumerator'
618
+ COMMANDS.keys.sort.enum_slice(12).each do |slice|
619
+ print slice.join(' ')
620
+ print "\n"
621
+ end
622
+ end
623
+ print "\n"
624
+ end
625
+
626
+ end
627
+ end