]> git.ipfire.org Git - thirdparty/bash.git/blame - examples/obashdb/bashdb
Bash-4.2 patch 45
[thirdparty/bash.git] / examples / obashdb / bashdb
CommitLineData
3185942a
JA
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.
7117c2d2 26
3185942a
JA
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
7117c2d2 33
3185942a
JA
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]
7117c2d2 44
3185942a
JA
45echo 'Bash Debugger version 1.2.4'
46
47export _dbname=${0##*/}
48
49if test $# -lt 1; then
50 echo "$_dbname: Usage: $_dbname filename" >&2
51 exit 1
52fi
7117c2d2
JA
53
54_guineapig=$1
55
3185942a
JA
56if test ! -r $1; then
57 echo "$_dbname: Cannot read file '$_guineapig'." >&2
58 exit 1
59fi
60
7117c2d2
JA
61shift
62
3185942a
JA
63__debug=${TMPDIR-/tmp}/bashdb.$$
64sed -e '/^# bashdb - Bash shell debugger/,/^# -- DO NOT DELETE THIS LINE -- /d' "$0" > $__debug
65cat $_guineapig >> $__debug
66exec ${DEBUG_SHELL-bash} $__debug $_guineapig "$@"
7117c2d2 67
3185942a
JA
68exit 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
79shift
80
81shopt -s extglob # turn on extglob so we can parse the debugger funcs
82
83function _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
131function _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
181function _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
195function _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
222function _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
233function _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
248function _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
269function _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...]
296function _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
310function _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
389function _at_linenumbp
390{
391 [[ -n ${_linebp[$_curline]} ]]
392}
393
394function _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
413function _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
423function _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
484function _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
494function _msg
495{
496 echo -e "$@" >&2
497}
498
499function _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
520function _cleanup
521{
522 rm -f $__debug $_potbelliedpig 2> /dev/null
523}
524
525function _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
551shopt -u extglob
552
553HISTFILE=~/.bashdb_history
554set -o history
555set +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=()
563let _trace=0
564let _i=1
565
566# Be careful about quoted newlines
567_potbelliedpig=${TMPDIR-/tmp}/${_guineapig/*\//}.$$
568sed 's,\\$,\\\\,' $_guineapig > $_potbelliedpig
569
570_msg "Reading source from file: $_guineapig"
571while read; do
572 _lines[$_i]=$REPLY
573 let _i=$_i+1
574done < $_potbelliedpig
575
576trap _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
579let _steps=1
580LINENO=-1
581trap '_steptrap $LINENO' DEBUG