lib/debug.rb
DEFINITIONS
This source file includes following functions.
1 # Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
2 # Copyright (C) 2000 Information-technology Promotion Agency, Japan
3
4 if $SAFE > 0
5 STDERR.print "-r debug.rb is not available in safe mode\n"
6 exit 1
7 end
8
9 require 'tracer'
10 require 'pp'
11
12 class Tracer
13 def Tracer.trace_func(*vars)
14 Single.trace_func(*vars)
15 end
16 end
17
18 SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
19
20 class DEBUGGER__
21 class Mutex
22 def initialize
23 @locker = nil
24 @waiting = []
25 @locked = false;
26 end
27
28 def locked?
29 @locked
30 end
31
32 def lock
33 return if @locker == Thread.current
34 while (Thread.critical = true; @locked)
35 @waiting.push Thread.current
36 Thread.stop
37 end
38 @locked = true
39 @locker = Thread.current
40 Thread.critical = false
41 self
42 end
43
44 def unlock
45 return unless @locked
46 unless @locker == Thread.current
47 raise RuntimeError, "unlocked by other"
48 end
49 Thread.critical = true
50 t = @waiting.shift
51 @locked = false
52 @locker = nil
53 Thread.critical = false
54 t.run if t
55 self
56 end
57 end
58 MUTEX = Mutex.new
59
60 class Context
61 DEBUG_LAST_CMD = []
62
63 begin
64 require 'readline'
65 def readline(prompt, hist)
66 Readline::readline(prompt, hist)
67 end
68 rescue LoadError
69 def readline(prompt, hist)
70 STDOUT.print prompt
71 STDOUT.flush
72 line = STDIN.gets
73 exit unless line
74 line.chomp!
75 line
76 end
77 USE_READLINE = false
78 end
79
80 def initialize
81 if Thread.current == Thread.main
82 @stop_next = 1
83 else
84 @stop_next = 0
85 end
86 @last_file = nil
87 @file = nil
88 @line = nil
89 @no_step = nil
90 @frames = []
91 @finish_pos = 0
92 @trace = false
93 @catch = "StandardError"
94 @suspend_next = false
95 end
96
97 def stop_next(n=1)
98 @stop_next = n
99 end
100
101 def set_suspend
102 @suspend_next = true
103 end
104
105 def clear_suspend
106 @suspend_next = false
107 end
108
109 def suspend_all
110 DEBUGGER__.suspend
111 end
112
113 def resume_all
114 DEBUGGER__.resume
115 end
116
117 def check_suspend
118 while (Thread.critical = true; @suspend_next)
119 DEBUGGER__.waiting.push Thread.current
120 @suspend_next = false
121 Thread.stop
122 end
123 Thread.critical = false
124 end
125
126 def trace?
127 @trace
128 end
129
130 def set_trace(arg)
131 @trace = arg
132 end
133
134 def stdout
135 DEBUGGER__.stdout
136 end
137
138 def break_points
139 DEBUGGER__.break_points
140 end
141
142 def display
143 DEBUGGER__.display
144 end
145
146 def context(th)
147 DEBUGGER__.context(th)
148 end
149
150 def set_trace_all(arg)
151 DEBUGGER__.set_trace(arg)
152 end
153
154 def set_last_thread(th)
155 DEBUGGER__.set_last_thread(th)
156 end
157
158 def debug_eval(str, binding)
159 begin
160 val = eval(str, binding)
161 val
162 rescue StandardError, ScriptError
163 at = eval("caller(0)", binding)
164 stdout.printf "%s:%s\n", at.shift, $!.to_s.sub(/\(eval\):1:(in `.*?':)?/, '') #`
165 for i in at
166 stdout.printf "\tfrom %s\n", i
167 end
168 throw :debug_error
169 end
170 end
171
172 def debug_silent_eval(str, binding)
173 begin
174 val = eval(str, binding)
175 val
176 rescue StandardError, ScriptError
177 nil
178 end
179 end
180
181 def var_list(ary, binding)
182 ary.sort!
183 for v in ary
184 stdout.printf " %s => %s\n", v, eval(v, binding).inspect
185 end
186 end
187
188 def debug_variable_info(input, binding)
189 case input
190 when /^\s*g(?:lobal)?$/
191 var_list(global_variables, binding)
192
193 when /^\s*l(?:ocal)?$/
194 var_list(eval("local_variables", binding), binding)
195
196 when /^\s*i(?:nstance)?\s+/
197 obj = debug_eval($', binding)
198 var_list(obj.instance_variables, obj.instance_eval{binding()})
199
200 when /^\s*c(?:onst(?:ant)?)?\s+/
201 obj = debug_eval($', binding)
202 unless obj.kind_of? Module
203 stdout.print "Should be Class/Module: ", $', "\n"
204 else
205 var_list(obj.constants, obj.module_eval{binding()})
206 end
207 end
208 end
209
210 def debug_method_info(input, binding)
211 case input
212 when /^i(:?nstance)?\s+/
213 obj = debug_eval($', binding)
214
215 len = 0
216 for v in obj.methods.sort
217 len += v.size + 1
218 if len > 70
219 len = v.size + 1
220 stdout.print "\n"
221 end
222 stdout.print v, " "
223 end
224 stdout.print "\n"
225
226 else
227 obj = debug_eval(input, binding)
228 unless obj.kind_of? Module
229 stdout.print "Should be Class/Module: ", input, "\n"
230 else
231 len = 0
232 for v in obj.instance_methods.sort
233 len += v.size + 1
234 if len > 70
235 len = v.size + 1
236 stdout.print "\n"
237 end
238 stdout.print v, " "
239 end
240 stdout.print "\n"
241 end
242 end
243 end
244
245 def thnum
246 num = DEBUGGER__.instance_eval{@thread_list[Thread.current]}
247 unless num
248 DEBUGGER__.make_thread_list
249 num = DEBUGGER__.instance_eval{@thread_list[Thread.current]}
250 end
251 num
252 end
253
254 def debug_command(file, line, id, binding)
255 MUTEX.lock
256 set_last_thread(Thread.current)
257 frame_pos = 0
258 binding_file = file
259 binding_line = line
260 previous_line = nil
261 if (ENV['EMACS'] == 't')
262 stdout.printf "\032\032%s:%d:\n", binding_file, binding_line
263 else
264 stdout.printf "%s:%d:%s", binding_file, binding_line,
265 line_at(binding_file, binding_line)
266 end
267 @frames[0] = [binding, file, line, id]
268 display_expressions(binding)
269 prompt = true
270 while prompt and input = readline("(rdb:%d) "%thnum(), true)
271 catch(:debug_error) do
272 if input == ""
273 input = DEBUG_LAST_CMD[0]
274 stdout.print input, "\n"
275 else
276 DEBUG_LAST_CMD[0] = input
277 end
278
279 case input
280 when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/
281 if defined?( $2 )
282 if $1 == 'on'
283 set_trace_all true
284 else
285 set_trace_all false
286 end
287 elsif defined?( $1 )
288 if $1 == 'on'
289 set_trace true
290 else
291 set_trace false
292 end
293 end
294 if trace?
295 stdout.print "Trace on.\n"
296 else
297 stdout.print "Trace off.\n"
298 end
299
300 when /^\s*b(?:reak)?\s+(?:(.+):)?(.+)$/
301 pos = $2
302 file = File.basename($1) if $1
303 if pos =~ /^\d+$/
304 pname = pos
305 pos = pos.to_i
306 else
307 pname = pos = pos.intern.id2name
308 end
309 break_points.push [true, 0, file, pos]
310 stdout.printf "Set breakpoint %d at %s:%s\n", break_points.size, file, pname
311
312 when /^\s*wat(?:ch)?\s+(.+)$/
313 exp = $1
314 break_points.push [true, 1, exp]
315 stdout.printf "Set watchpoint %d\n", break_points.size, exp
316
317 when /^\s*b(?:reak)?$/
318 if break_points.find{|b| b[1] == 0}
319 n = 1
320 stdout.print "Breakpoints:\n"
321 for b in break_points
322 if b[0] and b[1] == 0
323 stdout.printf " %d %s:%s\n", n, b[2], b[3]
324 end
325 n += 1
326 end
327 end
328 if break_points.find{|b| b[1] == 1}
329 n = 1
330 stdout.print "\n"
331 stdout.print "Watchpoints:\n"
332 for b in break_points
333 if b[0] and b[1] == 1
334 stdout.printf " %d %s\n", n, b[2]
335 end
336 n += 1
337 end
338 end
339 if break_points.size == 0
340 stdout.print "No breakpoints\n"
341 else
342 stdout.print "\n"
343 end
344
345 when /^\s*del(?:ete)?(?:\s+(\d+))?$/
346 pos = $1
347 unless pos
348 input = readline("Clear all breakpoints? (y/n) ", false)
349 if input == "y"
350 for b in break_points
351 b[0] = false
352 end
353 end
354 else
355 pos = pos.to_i
356 if break_points[pos-1]
357 break_points[pos-1][0] = false
358 else
359 stdout.printf "Breakpoint %d is not defined\n", pos
360 end
361 end
362
363 when /^\s*disp(?:lay)?\s+(.+)$/
364 exp = $1
365 display.push [true, exp]
366 stdout.printf "%d: ", display.size
367 display_expression(exp, binding)
368
369 when /^\s*disp(?:lay)?$/
370 display_expressions(binding)
371
372 when /^\s*undisp(?:lay)?(?:\s+(\d+))?$/
373 pos = $1
374 unless pos
375 input = readline("Clear all expressions? (y/n) ", false)
376 if input == "y"
377 for d in display
378 d[0] = false
379 end
380 end
381 else
382 pos = pos.to_i
383 if display[pos-1]
384 display[pos-1][0] = false
385 else
386 stdout.printf "Display expression %d is not defined\n", pos
387 end
388 end
389
390 when /^\s*c(?:ont)?$/
391 prompt = false
392
393 when /^\s*s(?:tep)?(?:\s+(\d+))?$/
394 if $1
395 lev = $1.to_i
396 else
397 lev = 1
398 end
399 @stop_next = lev
400 prompt = false
401
402 when /^\s*n(?:ext)?(?:\s+(\d+))?$/
403 if $1
404 lev = $1.to_i
405 else
406 lev = 1
407 end
408 @stop_next = lev
409 @no_step = @frames.size - frame_pos
410 prompt = false
411
412 when /^\s*w(?:here)?$/, /^\s*f(?:rame)?$/
413 display_frames(frame_pos)
414
415 when /^\s*l(?:ist)?(?:\s+(.+))?$/
416 if not $1
417 b = previous_line ? previous_line + 10 : binding_line - 5
418 e = b + 9
419 elsif $1 == '-'
420 b = previous_line ? previous_line - 10 : binding_line - 5
421 e = b + 9
422 else
423 b, e = $1.split(/[-,]/)
424 if e
425 b = b.to_i
426 e = e.to_i
427 else
428 b = b.to_i - 5
429 e = b + 9
430 end
431 end
432 previous_line = b
433 display_list(b, e, binding_file, binding_line)
434
435 when /^\s*up(?:\s+(\d+))?$/
436 previous_line = nil
437 if $1
438 lev = $1.to_i
439 else
440 lev = 1
441 end
442 frame_pos += lev
443 if frame_pos >= @frames.size
444 frame_pos = @frames.size - 1
445 stdout.print "At toplevel\n"
446 end
447 binding, binding_file, binding_line = @frames[frame_pos]
448 stdout.print format_frame(frame_pos)
449
450 when /^\s*down(?:\s+(\d+))?$/
451 previous_line = nil
452 if $1
453 lev = $1.to_i
454 else
455 lev = 1
456 end
457 frame_pos -= lev
458 if frame_pos < 0
459 frame_pos = 0
460 stdout.print "At stack bottom\n"
461 end
462 binding, binding_file, binding_line = @frames[frame_pos]
463 stdout.print format_frame(frame_pos)
464
465 when /^\s*fin(?:ish)?$/
466 if frame_pos == @frames.size
467 stdout.print "\"finish\" not meaningful in the outermost frame.\n"
468 else
469 @finish_pos = @frames.size - frame_pos
470 frame_pos = 0
471 prompt = false
472 end
473
474 when /^\s*cat(?:ch)?(?:\s+(.+))?$/
475 if $1
476 excn = $1
477 if excn == 'off'
478 @catch = nil
479 stdout.print "Clear catchpoint.\n"
480 else
481 @catch = excn
482 stdout.printf "Set catchpoint %s.\n", @catch
483 end
484 else
485 if @catch
486 stdout.printf "Catchpoint %s.\n", @catch
487 else
488 stdout.print "No catchpoint.\n"
489 end
490 end
491
492 when /^\s*q(?:uit)?$/
493 input = readline("Really quit? (y/n) ", false)
494 if input == "y"
495 exit! # exit -> exit!: No graceful way to stop threads...
496 end
497
498 when /^\s*v(?:ar)?\s+/
499 debug_variable_info($', binding)
500
501 when /^\s*m(?:ethod)?\s+/
502 debug_method_info($', binding)
503
504 when /^\s*th(?:read)?\s+/
505 if DEBUGGER__.debug_thread_info($', binding) == :cont
506 prompt = false
507 end
508
509 when /^\s*pp\s+/
510 PP.pp(debug_eval($', binding), stdout)
511
512 when /^\s*p\s+/
513 stdout.printf "%s\n", debug_eval($', binding).inspect
514
515 when /^\s*h(?:elp)?$/
516 debug_print_help()
517
518 else
519 v = debug_eval(input, binding)
520 stdout.printf "%s\n", v.inspect unless (v == nil)
521 end
522 end
523 end
524 MUTEX.unlock
525 resume_all
526 end
527
528 def debug_print_help
529 stdout.print <<EOHELP
530 Debugger help v.-0.002b
531 Commands
532 b[reak] [file|method:]<line|method>
533 set breakpoint to some position
534 wat[ch] <expression> set watchpoint to some expression
535 cat[ch] <an Exception> set catchpoint to an exception
536 b[reak] list breakpoints
537 cat[ch] show catchpoint
538 del[ete][ nnn] delete some or all breakpoints
539 disp[lay] <expression> add expression into display expression list
540 undisp[lay][ nnn] delete one particular or all display expressions
541 c[ont] run until program ends or hit breakpoint
542 s[tep][ nnn] step (into methods) one line or till line nnn
543 n[ext][ nnn] go over one line or till line nnn
544 w[here] display frames
545 f[rame] alias for where
546 l[ist][ (-|nn-mm)] list program, - lists backwards
547 nn-mm lists given lines
548 up[ nn] move to higher frame
549 down[ nn] move to lower frame
550 fin[ish] return to outer frame
551 tr[ace] (on|off) set trace mode of current thread
552 tr[ace] (on|off) all set trace mode of all threads
553 q[uit] exit from debugger
554 v[ar] g[lobal] show global variables
555 v[ar] l[ocal] show local variables
556 v[ar] i[nstance] <object> show instance variables of object
557 v[ar] c[onst] <object> show constants of object
558 m[ethod] i[nstance] <obj> show methods of object
559 m[ethod] <class|module> show instance methods of class or module
560 th[read] l[ist] list all threads
561 th[read] c[ur[rent]] show current thread
562 th[read] [sw[itch]] <nnn> switch thread context to nnn
563 th[read] stop <nnn> stop thread nnn
564 th[read] resume <nnn> resume thread nnn
565 p expression evaluate expression and print its value
566 h[elp] print this help
567 <everything else> evaluate
568 EOHELP
569 end
570
571 def display_expressions(binding)
572 n = 1
573 for d in display
574 if d[0]
575 stdout.printf "%d: ", n
576 display_expression(d[1], binding)
577 end
578 n += 1
579 end
580 end
581
582 def display_expression(exp, binding)
583 stdout.printf "%s = %s\n", exp, debug_silent_eval(exp, binding).to_s
584 end
585
586 def frame_set_pos(file, line)
587 if @frames[0]
588 @frames[0][1] = file
589 @frames[0][2] = line
590 end
591 end
592
593 def display_frames(pos)
594 0.upto(@frames.size - 1) do |n|
595 if n == pos
596 stdout.print "--> "
597 else
598 stdout.print " "
599 end
600 stdout.print format_frame(n)
601 end
602 end
603
604 def format_frame(pos)
605 bind, file, line, id = @frames[pos]
606 sprintf "#%d %s:%s%s\n", pos + 1, file, line,
607 (id ? ":in `#{id.id2name}'" : "")
608 end
609
610 def display_list(b, e, file, line)
611 stdout.printf "[%d, %d] in %s\n", b, e, file
612 if lines = SCRIPT_LINES__[file] and lines != true
613 n = 0
614 b.upto(e) do |n|
615 if n > 0 && lines[n-1]
616 if n == line
617 stdout.printf "=> %d %s\n", n, lines[n-1].chomp
618 else
619 stdout.printf " %d %s\n", n, lines[n-1].chomp
620 end
621 end
622 end
623 else
624 stdout.printf "No sourcefile available for %s\n", file
625 end
626 end
627
628 def line_at(file, line)
629 lines = SCRIPT_LINES__[file]
630 if lines
631 return "\n" if lines == true
632 line = lines[line-1]
633 return "\n" unless line
634 return line
635 end
636 return "\n"
637 end
638
639 def debug_funcname(id)
640 if id.nil?
641 "toplevel"
642 else
643 id.id2name
644 end
645 end
646
647 def check_break_points(file, pos, binding, id)
648 return false if break_points.empty?
649 file = File.basename(file)
650 n = 1
651 for b in break_points
652 if b[0]
653 if b[1] == 0 and b[2] == file and b[3] == pos
654 stdout.printf "Breakpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos
655 return true
656 elsif b[1] == 1
657 if debug_silent_eval(b[2], binding)
658 stdout.printf "Watchpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos
659 return true
660 end
661 end
662 end
663 n += 1
664 end
665 return false
666 end
667
668 def excn_handle(file, line, id, binding)
669 stdout.printf "%s:%d: `%s' (%s)\n", file, line, $!, $!.type
670 if $!.type <= SystemExit
671 set_trace_func nil
672 exit
673 end
674
675 if @catch and ($!.type.ancestors.find { |e| e.to_s == @catch })
676 fs = @frames.size
677 tb = caller(0)[-fs..-1]
678 if tb
679 for i in tb
680 stdout.printf "\tfrom %s\n", i
681 end
682 end
683 suspend_all
684 debug_command(file, line, id, binding)
685 end
686 end
687
688 def trace_func(event, file, line, id, binding, klass)
689 Tracer.trace_func(event, file, line, id, binding, klass) if trace?
690 context(Thread.current).check_suspend
691 @file = file
692 @line = line
693 case event
694 when 'line'
695 frame_set_pos(file, line)
696 if !@no_step or @frames.size == @no_step
697 @stop_next -= 1
698 elsif @frames.size < @no_step
699 @stop_next = 0 # break here before leaving...
700 else
701 # nothing to do. skipped.
702 end
703 if @stop_next == 0 or check_break_points(file, line, binding, id)
704 @no_step = nil
705 suspend_all
706 debug_command(file, line, id, binding)
707 end
708
709 when 'call'
710 @frames.unshift [binding, file, line, id]
711 if check_break_points(file, id.id2name, binding, id) or
712 check_break_points(klass.to_s, id.id2name, binding, id)
713 suspend_all
714 debug_command(file, line, id, binding)
715 end
716
717 when 'c-call'
718 frame_set_pos(file, line)
719
720 when 'class'
721 @frames.unshift [binding, file, line, id]
722
723 when 'return', 'end'
724 if @frames.size == @finish_pos
725 @stop_next = 1
726 @finish_pos = 0
727 end
728 @frames.shift
729
730 when 'end'
731 @frames.shift
732
733 when 'raise'
734 excn_handle(file, line, id, binding)
735
736 end
737 @last_file = file
738 end
739 end
740
741 trap("INT") { DEBUGGER__.interrupt }
742 @last_thread = Thread::main
743 @max_thread = 1
744 @thread_list = {Thread::main => 1}
745 @break_points = []
746 @display = []
747 @waiting = []
748 @stdout = STDOUT
749
750 class << DEBUGGER__
751 def stdout
752 @stdout
753 end
754
755 def stdout=(s)
756 @stdout = s
757 end
758
759 def display
760 @display
761 end
762
763 def break_points
764 @break_points
765 end
766
767 def waiting
768 @waiting
769 end
770
771 def set_trace( arg )
772 Thread.critical = true
773 make_thread_list
774 for th in @thread_list
775 context(th[0]).set_trace arg
776 end
777 Thread.critical = false
778 arg
779 end
780
781 def set_last_thread(th)
782 @last_thread = th
783 end
784
785 def suspend
786 Thread.critical = true
787 make_thread_list
788 for th in @thread_list
789 next if th[0] == Thread.current
790 context(th[0]).set_suspend
791 end
792 Thread.critical = false
793 # Schedule other threads to suspend as soon as possible.
794 Thread.pass
795 end
796
797 def resume
798 Thread.critical = true
799 make_thread_list
800 for th in @thread_list
801 next if th[0] == Thread.current
802 context(th[0]).clear_suspend
803 end
804 waiting.each do |th|
805 th.run
806 end
807 waiting.clear
808 Thread.critical = false
809 # Schedule other threads to restart as soon as possible.
810 Thread.pass
811 end
812
813 def context(thread=Thread.current)
814 c = thread[:__debugger_data__]
815 unless c
816 thread[:__debugger_data__] = c = Context.new
817 end
818 c
819 end
820
821 def interrupt
822 context(@last_thread).stop_next
823 end
824
825 def get_thread(num)
826 th = @thread_list.index(num)
827 unless th
828 @stdout.print "No thread ##{num}\n"
829 throw :debug_error
830 end
831 th
832 end
833
834 def thread_list(num)
835 th = get_thread(num)
836 if th == Thread.current
837 @stdout.print "+"
838 else
839 @stdout.print " "
840 end
841 @stdout.printf "%d ", num
842 @stdout.print th.inspect, "\t"
843 file = context(th).instance_eval{@file}
844 if file
845 @stdout.print file,":",context(th).instance_eval{@line}
846 end
847 @stdout.print "\n"
848 end
849
850 def thread_list_all
851 for th in @thread_list.values.sort
852 thread_list(th)
853 end
854 end
855
856 def make_thread_list
857 hash = {}
858 for th in Thread::list
859 if @thread_list.key? th
860 hash[th] = @thread_list[th]
861 else
862 @max_thread += 1
863 hash[th] = @max_thread
864 end
865 end
866 @thread_list = hash
867 end
868
869 def debug_thread_info(input, binding)
870 case input
871 when /^l(?:ist)?/
872 make_thread_list
873 thread_list_all
874
875 when /^c(?:ur(?:rent)?)?$/
876 make_thread_list
877 thread_list(@thread_list[Thread.current])
878
879 when /^(?:sw(?:itch)?\s+)?(\d+)/
880 make_thread_list
881 th = get_thread($1.to_i)
882 if th == Thread.current
883 @stdout.print "It's the current thread.\n"
884 else
885 thread_list(@thread_list[th])
886 context(th).stop_next
887 th.run
888 return :cont
889 end
890
891 when /^stop\s+(\d+)/
892 make_thread_list
893 th = get_thread($1.to_i)
894 if th == Thread.current
895 @stdout.print "It's the current thread.\n"
896 elsif th.stop?
897 @stdout.print "Already stopped.\n"
898 else
899 thread_list(@thread_list[th])
900 context(th).suspend
901 end
902
903 when /^resume\s+(\d+)/
904 make_thread_list
905 th = get_thread($1.to_i)
906 if th == Thread.current
907 @stdout.print "It's the current thread.\n"
908 elsif !th.stop?
909 @stdout.print "Already running."
910 else
911 thread_list(@thread_list[th])
912 th.run
913 end
914 end
915 end
916 end
917
918 stdout.printf "Debug.rb\n"
919 stdout.printf "Emacs support available.\n\n"
920 set_trace_func proc { |event, file, line, id, binding, klass, *rest|
921 DEBUGGER__.context.trace_func event, file, line, id, binding, klass
922 }
923 end