581 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			581 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
#! /bin/bash
 | 
						|
# bashdb - Bash shell debugger
 | 
						|
#
 | 
						|
# Adapted from an idea in O'Reilly's `Learning the Korn Shell'
 | 
						|
# Copyright (C) 1993-1994 O'Reilly and Associates, Inc.
 | 
						|
# Copyright (C) 1998, 1999, 2001 Gary V. Vaughan <gvv@techie.com>>
 | 
						|
#
 | 
						|
# This program is free software; you can redistribute it and/or modify
 | 
						|
# it under the terms of the GNU General Public License as published by
 | 
						|
# the Free Software Foundation; either version 2 of the License, or
 | 
						|
# (at your option) any later version.
 | 
						|
#
 | 
						|
# This program is distributed in the hope that it will be useful, but
 | 
						|
# WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
# General Public License for more details.
 | 
						|
#
 | 
						|
# You should have received a copy of the GNU General Public License
 | 
						|
# along with this program; if not, write to the Free Software
 | 
						|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 | 
						|
#
 | 
						|
# As a special exception to the GNU General Public License, if you
 | 
						|
# distribute this file as part of a program that contains a
 | 
						|
# configuration script generated by Autoconf, you may include it under
 | 
						|
# the same distribution terms that you use for the rest of that program.
 | 
						|
 | 
						|
# NOTE:
 | 
						|
#
 | 
						|
# This program requires bash 2.x.
 | 
						|
# If bash 2.x is installed as "bash2", you can invoke  bashdb like this:
 | 
						|
#
 | 
						|
#   DEBUG_SHELL=/bin/bash2 /bin/bash2 bashdb script.sh
 | 
						|
 | 
						|
# TODO:
 | 
						|
#
 | 
						|
# break [regexp]
 | 
						|
# cond [break] [condition]
 | 
						|
# tbreak [regexp|+lines]
 | 
						|
# restart
 | 
						|
# Variable watchpoints
 | 
						|
# Instrument `source' and `.' files in $_potbelliedpig
 | 
						|
# be cleverer about lines we allow breakpoints to be set on
 | 
						|
# break [function_name]
 | 
						|
 | 
						|
echo 'Bash Debugger version 1.2.4'
 | 
						|
 | 
						|
export _dbname=${0##*/}
 | 
						|
 | 
						|
if test $# -lt 1; then
 | 
						|
  echo "$_dbname: Usage: $_dbname filename" >&2
 | 
						|
  exit 1
 | 
						|
fi
 | 
						|
 | 
						|
_guineapig=$1
 | 
						|
 | 
						|
if test ! -r $1; then
 | 
						|
  echo "$_dbname: Cannot read file '$_guineapig'." >&2
 | 
						|
  exit 1
 | 
						|
fi
 | 
						|
 | 
						|
shift
 | 
						|
 | 
						|
__debug=${TMPDIR-/tmp}/bashdb.$$
 | 
						|
sed -e '/^# bashdb - Bash shell debugger/,/^# -- DO NOT DELETE THIS LINE -- /d' "$0" > $__debug
 | 
						|
cat $_guineapig >> $__debug
 | 
						|
exec ${DEBUG_SHELL-bash} $__debug $_guineapig "$@"
 | 
						|
 | 
						|
exit 1
 | 
						|
 | 
						|
# -- DO NOT DELETE THIS LINE -- The program depends on it
 | 
						|
 | 
						|
#bashdb preamble
 | 
						|
# $1 name of the original guinea pig script
 | 
						|
 | 
						|
__debug=$0
 | 
						|
_guineapig=$1
 | 
						|
__steptrap_calls=0
 | 
						|
 | 
						|
shift
 | 
						|
 | 
						|
shopt -s extglob	# turn on extglob so we can parse the debugger funcs
 | 
						|
 | 
						|
function _steptrap
 | 
						|
{
 | 
						|
  local i=0
 | 
						|
 | 
						|
  _curline=$1
 | 
						|
 | 
						|
  if (( ++__steptrap_calls > 1 && $_curline == 1 )); then
 | 
						|
    return
 | 
						|
  fi
 | 
						|
 | 
						|
  if [ -n "$_disps" ]; then
 | 
						|
    while (( $i < ${#_disps[@]} ))
 | 
						|
    do
 | 
						|
      if [ -n "${_disps[$i]}" ]; then
 | 
						|
        _msg "${_disps[$i]}: \c"
 | 
						|
        eval _msg ${_disps[$i]}
 | 
						|
      fi
 | 
						|
      let i=$i+1
 | 
						|
    done
 | 
						|
  fi
 | 
						|
 | 
						|
  if (( $_trace )); then
 | 
						|
    _showline $_curline
 | 
						|
  fi
 | 
						|
 | 
						|
  if (( $_steps >= 0 )); then
 | 
						|
    let _steps="$_steps - 1"
 | 
						|
  fi
 | 
						|
 | 
						|
  if _at_linenumbp ; then
 | 
						|
    _msg "Reached breakpoint at line $_curline"
 | 
						|
    _showline $_curline
 | 
						|
    _cmdloop
 | 
						|
  elif [ -n "$_brcond" ] && eval $_brcond; then
 | 
						|
    _msg "Break condition $_brcond true at line $_curline"
 | 
						|
    _showline $_curline
 | 
						|
    _cmdloop
 | 
						|
  elif (( $_steps == 0 )); then
 | 
						|
    # Assuming a real script will have the "#! /bin/sh" at line 1,
 | 
						|
    # assume that when $_curline == 1 we are inside backticks.
 | 
						|
    if (( ! $_trace )); then
 | 
						|
      _msg "Stopped at line $_curline"
 | 
						|
      _showline $_curline
 | 
						|
    fi
 | 
						|
    _cmdloop
 | 
						|
  fi
 | 
						|
}
 | 
						|
 | 
						|
function _setbp
 | 
						|
{
 | 
						|
  local i f line _x
 | 
						|
 | 
						|
  if [ -z "$1" ]; then
 | 
						|
    _listbp
 | 
						|
    return
 | 
						|
  fi
 | 
						|
 | 
						|
  eval "$_seteglob"
 | 
						|
 | 
						|
  if [[ $1 == *(\+)[1-9]*([0-9]) ]]; then
 | 
						|
    case $1 in
 | 
						|
    +*)
 | 
						|
      # normalize argument, then double it (+2 -> +2 + 2 = 4)
 | 
						|
      _x=${1##*([!1-9])}	# cut off non-numeric prefix
 | 
						|
      _x=${x%%*([!0-9])}	# cut off non-numeric suffix
 | 
						|
      f=$(( $1 + $_x ))
 | 
						|
      ;;
 | 
						|
    *)
 | 
						|
      f=$(( $1 ))
 | 
						|
      ;;
 | 
						|
    esac
 | 
						|
 | 
						|
    # find the next valid line
 | 
						|
    line="${_lines[$f]}"
 | 
						|
    while _invalidbreakp $f
 | 
						|
    do
 | 
						|
      (( f++ ))
 | 
						|
      line="${_lines[$f]}"
 | 
						|
    done
 | 
						|
 | 
						|
    if (( $f != $1 ))
 | 
						|
    then
 | 
						|
      _msg "Line $1 is not a valid breakpoint"
 | 
						|
    fi
 | 
						|
 | 
						|
    if [ -n "${_lines[$f]}" ]; then
 | 
						|
      _linebp[$1]=$1;
 | 
						|
      _msg "Breakpoint set at line $f"
 | 
						|
    else
 | 
						|
      _msg "Breakpoints can only be set on executable lines"
 | 
						|
    fi
 | 
						|
  else
 | 
						|
    _msg "Please specify a numeric line number"
 | 
						|
  fi
 | 
						|
 | 
						|
  eval "$_resteglob"
 | 
						|
}
 | 
						|
 | 
						|
function _listbp
 | 
						|
{
 | 
						|
  local i
 | 
						|
  
 | 
						|
  if [ -n "$_linebp" ]; then
 | 
						|
    _msg "Breakpoints:"
 | 
						|
    for i in ${_linebp[*]}; do
 | 
						|
      _showline $i
 | 
						|
    done
 | 
						|
  else
 | 
						|
    _msg "No breakpoints have been set"
 | 
						|
  fi
 | 
						|
}
 | 
						|
 | 
						|
function _clearbp
 | 
						|
{
 | 
						|
  local i
 | 
						|
 | 
						|
  if [ -z "$1" ]; then
 | 
						|
    read -e -p "Delete all breakpoints? "
 | 
						|
    case $REPLY in
 | 
						|
    [yY]*)
 | 
						|
      unset _linebp[*]
 | 
						|
      _msg "All breakpoints have been cleared"
 | 
						|
      ;;
 | 
						|
    esac
 | 
						|
    return 0
 | 
						|
  fi
 | 
						|
 | 
						|
  eval "$_seteglob"
 | 
						|
 | 
						|
  if [[ $1 == [1-9]*([0-9]) ]]; then
 | 
						|
    unset _linebp[$1]
 | 
						|
    _msg "Breakpoint cleared at line $1"
 | 
						|
  else
 | 
						|
    _msg "Please specify a numeric line number"
 | 
						|
  fi
 | 
						|
 | 
						|
  eval "$_resteglob"
 | 
						|
}
 | 
						|
 | 
						|
function _setbc
 | 
						|
{
 | 
						|
  if (( $# > 0 )); then
 | 
						|
    _brcond=$@
 | 
						|
    _msg "Break when true: $_brcond"
 | 
						|
  else
 | 
						|
    _brcond=
 | 
						|
    _msg "Break condition cleared"
 | 
						|
  fi
 | 
						|
}
 | 
						|
 | 
						|
function _setdisp
 | 
						|
{
 | 
						|
  if [ -z "$1" ]; then
 | 
						|
    _listdisp
 | 
						|
  else
 | 
						|
    _disps[${#_disps[@]}]="$1"
 | 
						|
    if (( ${#_disps[@]} < 10 ))
 | 
						|
    then
 | 
						|
      _msg " ${#_disps[@]}: $1"
 | 
						|
    else
 | 
						|
      _msg "${#_disps[@]}: $1"
 | 
						|
    fi
 | 
						|
  fi
 | 
						|
}
 | 
						|
 | 
						|
function _listdisp
 | 
						|
{
 | 
						|
  local i=0 j
 | 
						|
  
 | 
						|
  if [ -n "$_disps" ]; then
 | 
						|
    while (( $i < ${#_disps[@]} ))
 | 
						|
    do
 | 
						|
      let j=$i+1
 | 
						|
    if (( ${#_disps[@]} < 10 ))
 | 
						|
    then
 | 
						|
      _msg " $j: ${_disps[$i]}"
 | 
						|
    else
 | 
						|
      _msg "$j: ${_disps[$i]}"
 | 
						|
    fi
 | 
						|
      let i=$j
 | 
						|
    done
 | 
						|
  else
 | 
						|
    _msg "No displays have been set"
 | 
						|
  fi
 | 
						|
}
 | 
						|
 | 
						|
function _cleardisp
 | 
						|
{
 | 
						|
  if (( $# < 1 )) ; then
 | 
						|
    read -e -p "Delete all display expressions? "
 | 
						|
    case $REPLY in
 | 
						|
    [Yy]*)
 | 
						|
      unset _disps[*]
 | 
						|
      _msg "All breakpoints have been cleared"
 | 
						|
      ;;
 | 
						|
    esac
 | 
						|
    return 0
 | 
						|
  fi
 | 
						|
 | 
						|
  eval "$_seteglob"
 | 
						|
 | 
						|
  if [[ $1 == [1-9]*([0-9]) ]]; then
 | 
						|
    unset _disps[$1]
 | 
						|
    _msg "Display $i has been cleared"
 | 
						|
  else
 | 
						|
    _listdisp
 | 
						|
    _msg "Please specify a numeric display number"
 | 
						|
  fi
 | 
						|
 | 
						|
  eval "$_resteglob"
 | 
						|
}   
 | 
						|
 | 
						|
# usage _ftrace -u funcname [funcname...]
 | 
						|
function _ftrace
 | 
						|
{
 | 
						|
  local _opt=-t _tmsg="enabled" _func 
 | 
						|
  if [[ $1 == -u ]]; then
 | 
						|
	_opt=+t
 | 
						|
	_tmsg="disabled"
 | 
						|
	shift
 | 
						|
  fi
 | 
						|
  for _func; do
 | 
						|
	  declare -f $_opt $_func
 | 
						|
	  _msg "Tracing $_tmsg for function $_func"
 | 
						|
  done
 | 
						|
}
 | 
						|
 | 
						|
function _cmdloop
 | 
						|
{
 | 
						|
  local cmd args
 | 
						|
 | 
						|
  while read -e -p "bashdb> " cmd args; do
 | 
						|
    test -n "$cmd" && history -s "$cmd $args"	# save on history list
 | 
						|
    test -n "$cmd" || { set $_lastcmd; cmd=$1; shift; args=$*; }
 | 
						|
    if [ -n "$cmd" ]
 | 
						|
    then
 | 
						|
      case $cmd in
 | 
						|
	b|br|bre|brea|break)
 | 
						|
	  _setbp $args
 | 
						|
	  _lastcmd="break $args"
 | 
						|
	  ;;
 | 
						|
	co|con)
 | 
						|
	  _msg "ambiguous command: '$cmd', condition, continue?"
 | 
						|
	  ;;
 | 
						|
	cond|condi|condit|conditi|conditio|condition)
 | 
						|
	  _setbc $args
 | 
						|
	  _lastcmd="condition $args"
 | 
						|
	  ;;
 | 
						|
	c|cont|conti|contin|continu|continue)
 | 
						|
	  _lastcmd="continue"
 | 
						|
	  return
 | 
						|
	  ;;
 | 
						|
	d)
 | 
						|
	  _msg "ambiguous command: '$cmd', delete, display?"
 | 
						|
	  ;;
 | 
						|
	de|del|dele|delet|delete)
 | 
						|
	  _clearbp $args
 | 
						|
	  _lastcmd="delete $args"
 | 
						|
	  ;;
 | 
						|
	di|dis|disp|displ|displa|display)
 | 
						|
	  _setdisp $args
 | 
						|
	  _lastcmd="display $args"
 | 
						|
	  ;;
 | 
						|
	f|ft|ftr|ftra|ftrace)
 | 
						|
	  _ftrace $args
 | 
						|
	  _lastcmd="ftrace $args"
 | 
						|
	  ;;
 | 
						|
	\?|h|he|hel|help)
 | 
						|
	  _menu
 | 
						|
	  _lastcmd="help"
 | 
						|
	  ;;
 | 
						|
	l|li|lis|list)
 | 
						|
	  _displayscript $args
 | 
						|
	  # _lastcmd is set in the _displayscript function
 | 
						|
	  ;;
 | 
						|
	p|pr|pri|prin|print)
 | 
						|
	  _examine $args
 | 
						|
	  _lastcmd="print $args"
 | 
						|
	  ;;
 | 
						|
	q|qu|qui|quit)
 | 
						|
	  exit
 | 
						|
	  ;;
 | 
						|
	s|st|ste|step|n|ne|nex|next)
 | 
						|
	  let _steps=${args:-1}
 | 
						|
	  _lastcmd="next $args"
 | 
						|
	  return
 | 
						|
	  ;;
 | 
						|
	t|tr|tra|trac|trace)
 | 
						|
	  _xtrace
 | 
						|
	  ;;
 | 
						|
	u|un|und|undi|undis|undisp|undispl|undispla|undisplay)
 | 
						|
	  _cleardisp $args
 | 
						|
	  _lastcmd="undisplay $args"
 | 
						|
	  ;;
 | 
						|
	!*)
 | 
						|
	  eval ${cmd#!} $args
 | 
						|
	  _lastcmd="$cmd $args"
 | 
						|
	  ;;
 | 
						|
	*)
 | 
						|
	  _msg "Invalid command: '$cmd'"
 | 
						|
	  ;;
 | 
						|
      esac
 | 
						|
    fi
 | 
						|
  done
 | 
						|
}
 | 
						|
 | 
						|
function _at_linenumbp
 | 
						|
{
 | 
						|
  [[ -n ${_linebp[$_curline]} ]]
 | 
						|
}
 | 
						|
 | 
						|
function _invalidbreakp
 | 
						|
{
 | 
						|
  local line=${_lines[$1]}
 | 
						|
 | 
						|
  # XXX - should use shell patterns
 | 
						|
  if test -z "$line" \
 | 
						|
      || expr "$line" : '[ \t]*#.*' > /dev/null \
 | 
						|
      || expr "$line" : '[ \t]*;;[ \t]*$' > /dev/null \
 | 
						|
      || expr "$line" : '[ \t]*[^)]*)[ \t]*$' > /dev/null \
 | 
						|
      || expr "$line" : '[ \t]*;;[ \t]*#.**$' > /dev/null \
 | 
						|
      || expr "$line" : '[ \t]*[^)]*)[ \t]*;;[ \t]*$' > /dev/null \
 | 
						|
      || expr "$line" : '[ \t]*[^)]*)[ \t]*;;*[ \t]*#.*$' > /dev/null
 | 
						|
  then
 | 
						|
    return 0
 | 
						|
  fi
 | 
						|
 | 
						|
  return 1
 | 
						|
}
 | 
						|
 | 
						|
function _examine
 | 
						|
{
 | 
						|
  if [ -n "$*" ]; then
 | 
						|
    _msg "$args: \c"
 | 
						|
    eval _msg $args
 | 
						|
  else
 | 
						|
    _msg "Nothing to print"
 | 
						|
  fi
 | 
						|
}
 | 
						|
 | 
						|
function _displayscript
 | 
						|
{
 | 
						|
  local i j start end bp cl
 | 
						|
 | 
						|
  if (( $# == 1 )); then	# list 5 lines on either side of $1
 | 
						|
    if [ $1 = "%" ]; then
 | 
						|
      let start=1
 | 
						|
      let end=${#_lines[@]}
 | 
						|
    else
 | 
						|
      let start=$1-5
 | 
						|
      let end=$1+5
 | 
						|
    fi
 | 
						|
  elif (( $# > 1 )); then	# list between start and end
 | 
						|
    if [ $1 = "^" ]; then
 | 
						|
      let start=1
 | 
						|
    else
 | 
						|
      let start=$1
 | 
						|
    fi
 | 
						|
 | 
						|
    if [ $2 = "\$" ]; then
 | 
						|
      let end=${#_lines[@]}
 | 
						|
    else
 | 
						|
      let end=$2
 | 
						|
    fi
 | 
						|
  else				# list 5 lines on either side of current line
 | 
						|
    let start=$_curline-5
 | 
						|
    let end=$_curline+5
 | 
						|
  fi
 | 
						|
 | 
						|
  # normalize start and end
 | 
						|
  if (( $start < 1 )); then
 | 
						|
    start=1
 | 
						|
  fi
 | 
						|
  if (( $end > ${#_lines[@]} )); then
 | 
						|
    end=${#_lines[@]}
 | 
						|
  fi
 | 
						|
 | 
						|
  cl=$(( $end - $start ))
 | 
						|
  if (( $cl > ${LINES-24} )); then
 | 
						|
    pager=${PAGER-more}
 | 
						|
  else
 | 
						|
    pager=cat
 | 
						|
  fi
 | 
						|
  
 | 
						|
  i=$start
 | 
						|
  ( while (( $i <= $end )); do
 | 
						|
      _showline $i
 | 
						|
      let i=$i+1
 | 
						|
    done ) 2>&1 | $pager
 | 
						|
 | 
						|
  # calculate the next block of lines
 | 
						|
  start=$(( $end + 1 ))
 | 
						|
  end=$(( $start + 11 ))
 | 
						|
  if (( $end > ${#_lines[@]} ))
 | 
						|
  then
 | 
						|
    end=${#_lines[@]}
 | 
						|
  fi
 | 
						|
 | 
						|
  _lastcmd="list $start $end"
 | 
						|
}
 | 
						|
 | 
						|
function _xtrace
 | 
						|
{
 | 
						|
  let _trace="! $_trace"
 | 
						|
  if (( $_trace )); then
 | 
						|
    _msg "Execution trace on"
 | 
						|
  else
 | 
						|
    _msg "Execution trace off"
 | 
						|
  fi
 | 
						|
}
 | 
						|
	
 | 
						|
function _msg
 | 
						|
{
 | 
						|
  echo -e "$@" >&2
 | 
						|
}
 | 
						|
 | 
						|
function _showline
 | 
						|
{
 | 
						|
  local i=0 bp=' ' line=$1 cl=' '
 | 
						|
 | 
						|
  if [[ -n ${_linebp[$line]} ]]; then
 | 
						|
    bp='*'
 | 
						|
  fi
 | 
						|
 | 
						|
  if  (( $_curline == $line )); then
 | 
						|
    cl=">"
 | 
						|
  fi
 | 
						|
 | 
						|
  if (( $line < 100 )); then
 | 
						|
    _msg "${_guineapig/*\//}:$line   $bp $cl${_lines[$line]}"
 | 
						|
  elif (( $line < 10 )); then
 | 
						|
    _msg "${_guineapig/*\//}:$line  $bp $cl${_lines[$line]}"
 | 
						|
  elif (( $line > 0 )); then
 | 
						|
    _msg "${_guineapig/*\//}:$line $bp $cl${_lines[$line]}"
 | 
						|
  fi
 | 
						|
}
 | 
						|
 | 
						|
function _cleanup
 | 
						|
{
 | 
						|
  rm -f $__debug $_potbelliedpig 2> /dev/null
 | 
						|
}
 | 
						|
 | 
						|
function _menu
 | 
						|
{
 | 
						|
  _msg 'bashdb commands:
 | 
						|
	break N		set breakpoint at line N
 | 
						|
	break		list breakpoints & break condition
 | 
						|
	condition foo	set break condition to foo
 | 
						|
	condition	clear break condition
 | 
						|
	delete N	clear breakpoint at line N
 | 
						|
	delete		clear all breakpoints
 | 
						|
	display EXP	evaluate and display EXP for each debug step
 | 
						|
	display		show a list of display expressions
 | 
						|
	undisplay N	remove display expression N
 | 
						|
	list N M        display all lines of script between N and M
 | 
						|
	list N          display 5 lines of script either side of line N
 | 
						|
	list		display 5 lines if script either side of current line
 | 
						|
	continue	continue execution upto next breakpoint
 | 
						|
	next [N]	execute [N] statements (default 1)
 | 
						|
	print expr	prints the value of an expression
 | 
						|
	trace		toggle execution trace on/off
 | 
						|
	ftrace [-u] func	make the debugger step into function FUNC
 | 
						|
			(-u turns off tracing FUNC)
 | 
						|
	help		print this menu
 | 
						|
	! string	passes string to a shell
 | 
						|
	quit		quit'
 | 
						|
}
 | 
						|
 | 
						|
shopt -u extglob
 | 
						|
 | 
						|
HISTFILE=~/.bashdb_history
 | 
						|
set -o history
 | 
						|
set +H
 | 
						|
 | 
						|
# strings to save and restore the setting of `extglob' in debugger functions
 | 
						|
# that need it
 | 
						|
_seteglob='local __eopt=-u ; shopt -q extglob && __eopt=-s ; shopt -s extglob'
 | 
						|
_resteglob='shopt $__eopt extglob'
 | 
						|
 | 
						|
_linebp=()
 | 
						|
let _trace=0
 | 
						|
let _i=1
 | 
						|
 | 
						|
# Be careful about quoted newlines
 | 
						|
_potbelliedpig=${TMPDIR-/tmp}/${_guineapig/*\//}.$$
 | 
						|
sed 's,\\$,\\\\,' $_guineapig > $_potbelliedpig
 | 
						|
 | 
						|
_msg "Reading source from file: $_guineapig"
 | 
						|
while read; do
 | 
						|
  _lines[$_i]=$REPLY
 | 
						|
  let _i=$_i+1
 | 
						|
done < $_potbelliedpig
 | 
						|
 | 
						|
trap _cleanup EXIT
 | 
						|
# Assuming a real script will have the "#! /bin/sh" at line 1,
 | 
						|
# don't stop at line 1 on the first run
 | 
						|
let _steps=1
 | 
						|
LINENO=-1
 | 
						|
trap '_steptrap $LINENO' DEBUG
 |