]>
Commit | Line | Data |
---|---|---|
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 |
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 | |
7117c2d2 JA |
53 | |
54 | _guineapig=$1 | |
55 | ||
3185942a JA |
56 | if test ! -r $1; then |
57 | echo "$_dbname: Cannot read file '$_guineapig'." >&2 | |
58 | exit 1 | |
59 | fi | |
60 | ||
7117c2d2 JA |
61 | shift |
62 | ||
3185942a JA |
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 "$@" | |
7117c2d2 | 67 | |
3185942a JA |
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 |