Skip to content. | Skip to navigation

Navigation

You are here: Home / Support / Guides / Scripting / Bash / Debugging Bash / Stack Trace

Personal tools

Debugging Bash

Stack Trace

Bash 3.0 introduced several variables to support the Bash debugger. Short of running the debugger we can make use of several of these to construct a stack backtrace.

Two of the variables are only made available if Bash is in extended debugging mode, the shell option extdebug:

% shopt -s extdebug

The Variables and Commands

Commands

caller

caller is a command which returns the context of the current subroutine call. With an argument you can inspect the nth frame back up the stack.

Variables

BASH_ARGC

BASH_ARGC is an array which indicates how many arguments were put on the stack by the nth subroutine call. New values are pushed onto the front.

BASH_ARGV

BASH_ARGV is an array which the arguments put on the stack by the subroutine calls. All the subroutines in a single array with the per-subroutine arguments in reverse order. New values are pushed onto the front.

This sounds as complicated as it is. You need to use BASH_ARGC to tot up how many arguments were placed on the stack before the nth subroutine call and again to find out how many arguments are for this subroutine. You then need to reverse them.

For example, a function call:

foo f1 f2

which calls:

bar b1 b2 b3

will result in:

BASH_ARGC=( 3 2 )
BASH_ARGV=( b3 b2 b1 f2 f1 )
FUNCNAME

FUNCNAME is an array of the subroutine names on the stack. It is unset if there are no subroutines on the stack.

Stack Trace

The basic algorithm is to walk back up the stack frame determining the caller at each frame. We then extract the arguments for that context and can print out a useful trace:

stacktrace ()
{
   declare frame=0
   declare argv_offset=0

   while caller_info=( $(caller $frame) ) ; do

       if shopt -q extdebug ; then

           declare argv=()
           declare argc
           declare frame_argc

           for ((frame_argc=${BASH_ARGC[frame]},frame_argc--,argc=0; frame_argc >= 0; argc++, frame_argc--)) ; do
               argv[argc]=${BASH_ARGV[argv_offset+frame_argc]}
               case "${argv[argc]}" in
                   *[[:space:]]*) argv[argc]="'${argv[argc]}'" ;;
               esac
           done
           argv_offset=$((argv_offset + ${BASH_ARGC[frame]}))
           echo ":: ${caller_info[2]}: Line ${caller_info[0]}: ${caller_info[1]}(): ${FUNCNAME[frame]} ${argv[*]}"
       fi

       frame=$((frame+1))
   done

   if [[ $frame -eq 1 ]] ; then
       caller_info=( $(caller 0) )
       echo ":: ${caller_info[2]}: Line ${caller_info[0]}: ${caller_info[1]}"
   fi
}

Document Actions