Imported from ../bash-3.1.tar.gz.
[platform/upstream/bash.git] / examples / bashdb / bashdb
1 #! /bin/bash
2 # bashdb - Bash shell debugger
3 #
4 # Adapted from an idea in O'Reilly's `Learning the Korn Shell'
5 # Copyright (C) 1993-1994 O'Reilly and Associates, Inc.
6 # Copyright (C) 1998, 1999, 2001 Gary V. Vaughan <gvv@techie.com>>
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 #
22 # As a special exception to the GNU General Public License, if you
23 # distribute this file as part of a program that contains a
24 # configuration script generated by Autoconf, you may include it under
25 # the same distribution terms that you use for the rest of that program.
26
27 # NOTE:
28 #
29 # This program requires bash 2.x.
30 # If bash 2.x is installed as "bash2", you can invoke  bashdb like this:
31 #
32 #   DEBUG_SHELL=/bin/bash2 /bin/bash2 bashdb script.sh
33
34 # TODO:
35 #
36 # break [regexp]
37 # cond [break] [condition]
38 # tbreak [regexp|+lines]
39 # restart
40 # Variable watchpoints
41 # Instrument `source' and `.' files in $_potbelliedpig
42 # be cleverer about lines we allow breakpoints to be set on
43 # break [function_name]
44
45 echo 'Bash Debugger version 1.2.4'
46
47 export _dbname=${0##*/}
48
49 if test $# -lt 1; then
50   echo "$_dbname: Usage: $_dbname filename" >&2
51   exit 1
52 fi
53
54 _guineapig=$1
55
56 if test ! -r $1; then
57   echo "$_dbname: Cannot read file '$_guineapig'." >&2
58   exit 1
59 fi
60
61 shift
62
63 __debug=${TMPDIR-/tmp}/bashdb.$$
64 sed -e '/^# bashdb - Bash shell debugger/,/^# -- DO NOT DELETE THIS LINE -- /d' "$0" > $__debug
65 cat $_guineapig >> $__debug
66 exec ${DEBUG_SHELL-bash} $__debug $_guineapig "$@"
67
68 exit 1
69
70 # -- DO NOT DELETE THIS LINE -- The program depends on it
71
72 #bashdb preamble
73 # $1 name of the original guinea pig script
74
75 __debug=$0
76 _guineapig=$1
77 __steptrap_calls=0
78
79 shift
80
81 shopt -s extglob        # turn on extglob so we can parse the debugger funcs
82
83 function _steptrap
84 {
85   local i=0
86
87   _curline=$1
88
89   if (( ++__steptrap_calls > 1 && $_curline == 1 )); then
90     return
91   fi
92
93   if [ -n "$_disps" ]; then
94     while (( $i < ${#_disps[@]} ))
95     do
96       if [ -n "${_disps[$i]}" ]; then
97         _msg "${_disps[$i]}: \c"
98         eval _msg ${_disps[$i]}
99       fi
100       let i=$i+1
101     done
102   fi
103
104   if (( $_trace )); then
105     _showline $_curline
106   fi
107
108   if (( $_steps >= 0 )); then
109     let _steps="$_steps - 1"
110   fi
111
112   if _at_linenumbp ; then
113     _msg "Reached breakpoint at line $_curline"
114     _showline $_curline
115     _cmdloop
116   elif [ -n "$_brcond" ] && eval $_brcond; then
117     _msg "Break condition $_brcond true at line $_curline"
118     _showline $_curline
119     _cmdloop
120   elif (( $_steps == 0 )); then
121     # Assuming a real script will have the "#! /bin/sh" at line 1,
122     # assume that when $_curline == 1 we are inside backticks.
123     if (( ! $_trace )); then
124       _msg "Stopped at line $_curline"
125       _showline $_curline
126     fi
127     _cmdloop
128   fi
129 }
130
131 function _setbp
132 {
133   local i f line _x
134
135   if [ -z "$1" ]; then
136     _listbp
137     return
138   fi
139
140   eval "$_seteglob"
141
142   if [[ $1 == *(\+)[1-9]*([0-9]) ]]; then
143     case $1 in
144     +*)
145       # normalize argument, then double it (+2 -> +2 + 2 = 4)
146       _x=${1##*([!1-9])}        # cut off non-numeric prefix
147       _x=${x%%*([!0-9])}        # cut off non-numeric suffix
148       f=$(( $1 + $_x ))
149       ;;
150     *)
151       f=$(( $1 ))
152       ;;
153     esac
154
155     # find the next valid line
156     line="${_lines[$f]}"
157     while _invalidbreakp $f
158     do
159       (( f++ ))
160       line="${_lines[$f]}"
161     done
162
163     if (( $f != $1 ))
164     then
165       _msg "Line $1 is not a valid breakpoint"
166     fi
167
168     if [ -n "${_lines[$f]}" ]; then
169       _linebp[$1]=$1;
170       _msg "Breakpoint set at line $f"
171     else
172       _msg "Breakpoints can only be set on executable lines"
173     fi
174   else
175     _msg "Please specify a numeric line number"
176   fi
177
178   eval "$_resteglob"
179 }
180
181 function _listbp
182 {
183   local i
184   
185   if [ -n "$_linebp" ]; then
186     _msg "Breakpoints:"
187     for i in ${_linebp[*]}; do
188       _showline $i
189     done
190   else
191     _msg "No breakpoints have been set"
192   fi
193 }
194
195 function _clearbp
196 {
197   local i
198
199   if [ -z "$1" ]; then
200     read -e -p "Delete all breakpoints? "
201     case $REPLY in
202     [yY]*)
203       unset _linebp[*]
204       _msg "All breakpoints have been cleared"
205       ;;
206     esac
207     return 0
208   fi
209
210   eval "$_seteglob"
211
212   if [[ $1 == [1-9]*([0-9]) ]]; then
213     unset _linebp[$1]
214     _msg "Breakpoint cleared at line $1"
215   else
216     _msg "Please specify a numeric line number"
217   fi
218
219   eval "$_resteglob"
220 }
221
222 function _setbc
223 {
224   if (( $# > 0 )); then
225     _brcond=$@
226     _msg "Break when true: $_brcond"
227   else
228     _brcond=
229     _msg "Break condition cleared"
230   fi
231 }
232
233 function _setdisp
234 {
235   if [ -z "$1" ]; then
236     _listdisp
237   else
238     _disps[${#_disps[@]}]="$1"
239     if (( ${#_disps[@]} < 10 ))
240     then
241       _msg " ${#_disps[@]}: $1"
242     else
243       _msg "${#_disps[@]}: $1"
244     fi
245   fi
246 }
247
248 function _listdisp
249 {
250   local i=0 j
251   
252   if [ -n "$_disps" ]; then
253     while (( $i < ${#_disps[@]} ))
254     do
255       let j=$i+1
256     if (( ${#_disps[@]} < 10 ))
257     then
258       _msg " $j: ${_disps[$i]}"
259     else
260       _msg "$j: ${_disps[$i]}"
261     fi
262       let i=$j
263     done
264   else
265     _msg "No displays have been set"
266   fi
267 }
268
269 function _cleardisp
270 {
271   if (( $# < 1 )) ; then
272     read -e -p "Delete all display expressions? "
273     case $REPLY in
274     [Yy]*)
275       unset _disps[*]
276       _msg "All breakpoints have been cleared"
277       ;;
278     esac
279     return 0
280   fi
281
282   eval "$_seteglob"
283
284   if [[ $1 == [1-9]*([0-9]) ]]; then
285     unset _disps[$1]
286     _msg "Display $i has been cleared"
287   else
288     _listdisp
289     _msg "Please specify a numeric display number"
290   fi
291
292   eval "$_resteglob"
293 }   
294
295 # usage _ftrace -u funcname [funcname...]
296 function _ftrace
297 {
298   local _opt=-t _tmsg="enabled" _func 
299   if [[ $1 == -u ]]; then
300         _opt=+t
301         _tmsg="disabled"
302         shift
303   fi
304   for _func; do
305           declare -f $_opt $_func
306           _msg "Tracing $_tmsg for function $_func"
307   done
308 }
309
310 function _cmdloop
311 {
312   local cmd args
313
314   while read -e -p "bashdb> " cmd args; do
315     test -n "$cmd" && history -s "$cmd $args"   # save on history list
316     test -n "$cmd" || { set $_lastcmd; cmd=$1; shift; args=$*; }
317     if [ -n "$cmd" ]
318     then
319       case $cmd in
320         b|br|bre|brea|break)
321           _setbp $args
322           _lastcmd="break $args"
323           ;;
324         co|con)
325           _msg "ambiguous command: '$cmd', condition, continue?"
326           ;;
327         cond|condi|condit|conditi|conditio|condition)
328           _setbc $args
329           _lastcmd="condition $args"
330           ;;
331         c|cont|conti|contin|continu|continue)
332           _lastcmd="continue"
333           return
334           ;;
335         d)
336           _msg "ambiguous command: '$cmd', delete, display?"
337           ;;
338         de|del|dele|delet|delete)
339           _clearbp $args
340           _lastcmd="delete $args"
341           ;;
342         di|dis|disp|displ|displa|display)
343           _setdisp $args
344           _lastcmd="display $args"
345           ;;
346         f|ft|ftr|ftra|ftrace)
347           _ftrace $args
348           _lastcmd="ftrace $args"
349           ;;
350         \?|h|he|hel|help)
351           _menu
352           _lastcmd="help"
353           ;;
354         l|li|lis|list)
355           _displayscript $args
356           # _lastcmd is set in the _displayscript function
357           ;;
358         p|pr|pri|prin|print)
359           _examine $args
360           _lastcmd="print $args"
361           ;;
362         q|qu|qui|quit)
363           exit
364           ;;
365         s|st|ste|step|n|ne|nex|next)
366           let _steps=${args:-1}
367           _lastcmd="next $args"
368           return
369           ;;
370         t|tr|tra|trac|trace)
371           _xtrace
372           ;;
373         u|un|und|undi|undis|undisp|undispl|undispla|undisplay)
374           _cleardisp $args
375           _lastcmd="undisplay $args"
376           ;;
377         !*)
378           eval ${cmd#!} $args
379           _lastcmd="$cmd $args"
380           ;;
381         *)
382           _msg "Invalid command: '$cmd'"
383           ;;
384       esac
385     fi
386   done
387 }
388
389 function _at_linenumbp
390 {
391   [[ -n ${_linebp[$_curline]} ]]
392 }
393
394 function _invalidbreakp
395 {
396   local line=${_lines[$1]}
397
398   # XXX - should use shell patterns
399   if test -z "$line" \
400       || expr "$line" : '[ \t]*#.*' > /dev/null \
401       || expr "$line" : '[ \t]*;;[ \t]*$' > /dev/null \
402       || expr "$line" : '[ \t]*[^)]*)[ \t]*$' > /dev/null \
403       || expr "$line" : '[ \t]*;;[ \t]*#.**$' > /dev/null \
404       || expr "$line" : '[ \t]*[^)]*)[ \t]*;;[ \t]*$' > /dev/null \
405       || expr "$line" : '[ \t]*[^)]*)[ \t]*;;*[ \t]*#.*$' > /dev/null
406   then
407     return 0
408   fi
409
410   return 1
411 }
412
413 function _examine
414 {
415   if [ -n "$*" ]; then
416     _msg "$args: \c"
417     eval _msg $args
418   else
419     _msg "Nothing to print"
420   fi
421 }
422
423 function _displayscript
424 {
425   local i j start end bp cl
426
427   if (( $# == 1 )); then        # list 5 lines on either side of $1
428     if [ $1 = "%" ]; then
429       let start=1
430       let end=${#_lines[@]}
431     else
432       let start=$1-5
433       let end=$1+5
434     fi
435   elif (( $# > 1 )); then       # list between start and end
436     if [ $1 = "^" ]; then
437       let start=1
438     else
439       let start=$1
440     fi
441
442     if [ $2 = "\$" ]; then
443       let end=${#_lines[@]}
444     else
445       let end=$2
446     fi
447   else                          # list 5 lines on either side of current line
448     let start=$_curline-5
449     let end=$_curline+5
450   fi
451
452   # normalize start and end
453   if (( $start < 1 )); then
454     start=1
455   fi
456   if (( $end > ${#_lines[@]} )); then
457     end=${#_lines[@]}
458   fi
459
460   cl=$(( $end - $start ))
461   if (( $cl > ${LINES-24} )); then
462     pager=${PAGER-more}
463   else
464     pager=cat
465   fi
466   
467   i=$start
468   ( while (( $i <= $end )); do
469       _showline $i
470       let i=$i+1
471     done ) 2>&1 | $pager
472
473   # calculate the next block of lines
474   start=$(( $end + 1 ))
475   end=$(( $start + 11 ))
476   if (( $end > ${#_lines[@]} ))
477   then
478     end=${#_lines[@]}
479   fi
480
481   _lastcmd="list $start $end"
482 }
483
484 function _xtrace
485 {
486   let _trace="! $_trace"
487   if (( $_trace )); then
488     _msg "Execution trace on"
489   else
490     _msg "Execution trace off"
491   fi
492 }
493         
494 function _msg
495 {
496   echo -e "$@" >&2
497 }
498
499 function _showline
500 {
501   local i=0 bp=' ' line=$1 cl=' '
502
503   if [[ -n ${_linebp[$line]} ]]; then
504     bp='*'
505   fi
506
507   if  (( $_curline == $line )); then
508     cl=">"
509   fi
510
511   if (( $line < 100 )); then
512     _msg "${_guineapig/*\//}:$line   $bp $cl${_lines[$line]}"
513   elif (( $line < 10 )); then
514     _msg "${_guineapig/*\//}:$line  $bp $cl${_lines[$line]}"
515   elif (( $line > 0 )); then
516     _msg "${_guineapig/*\//}:$line $bp $cl${_lines[$line]}"
517   fi
518 }
519
520 function _cleanup
521 {
522   rm -f $__debug $_potbelliedpig 2> /dev/null
523 }
524
525 function _menu
526 {
527   _msg 'bashdb commands:
528         break N         set breakpoint at line N
529         break           list breakpoints & break condition
530         condition foo   set break condition to foo
531         condition       clear break condition
532         delete N        clear breakpoint at line N
533         delete          clear all breakpoints
534         display EXP     evaluate and display EXP for each debug step
535         display         show a list of display expressions
536         undisplay N     remove display expression N
537         list N M        display all lines of script between N and M
538         list N          display 5 lines of script either side of line N
539         list            display 5 lines if script either side of current line
540         continue        continue execution upto next breakpoint
541         next [N]        execute [N] statements (default 1)
542         print expr      prints the value of an expression
543         trace           toggle execution trace on/off
544         ftrace [-u] func        make the debugger step into function FUNC
545                         (-u turns off tracing FUNC)
546         help            print this menu
547         ! string        passes string to a shell
548         quit            quit'
549 }
550
551 shopt -u extglob
552
553 HISTFILE=~/.bashdb_history
554 set -o history
555 set +H
556
557 # strings to save and restore the setting of `extglob' in debugger functions
558 # that need it
559 _seteglob='local __eopt=-u ; shopt -q extglob && __eopt=-s ; shopt -s extglob'
560 _resteglob='shopt $__eopt extglob'
561
562 _linebp=()
563 let _trace=0
564 let _i=1
565
566 # Be careful about quoted newlines
567 _potbelliedpig=${TMPDIR-/tmp}/${_guineapig/*\//}.$$
568 sed 's,\\$,\\\\,' $_guineapig > $_potbelliedpig
569
570 _msg "Reading source from file: $_guineapig"
571 while read; do
572   _lines[$_i]=$REPLY
573   let _i=$_i+1
574 done < $_potbelliedpig
575
576 trap _cleanup EXIT
577 # Assuming a real script will have the "#! /bin/sh" at line 1,
578 # don't stop at line 1 on the first run
579 let _steps=1
580 LINENO=-1
581 trap '_steptrap $LINENO' DEBUG