1# -*- shell-script -*-
2# Call Stack routines
3#
4#   Copyright (C) 2002-2006, 2008-2010, 2014, 2017
5#   Rocky Bernstein <rocky@gnu.org>
6#
7#   This program is free software; you can redistribute it and/or
8#   modify it under the terms of the GNU General Public License as
9#   published by the Free Software Foundation; either version 2, or
10#   (at your option) any later version.
11#
12#   This program is distributed in the hope that it will be useful,
13#   but WITHOUT ANY WARRANTY; without even the implied warranty of
14#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15#   General Public License for more details.
16#
17#   You should have received a copy of the GNU General Public License
18#   along with this program; see the file COPYING.  If not, write to
19#   the Free Software Foundation, 59 Temple Place, Suite 330, Boston,
20#   MA 02111 USA.
21
22#================ VARIABLE INITIALIZATIONS ====================#
23
24# _Dbg_stack_size: the number of entries on the call stack at the time
25# the hook was entered. Note that bash updates the stack inside the
26# debugger so it is important to save this value on entry. Also
27# note that the most recent entries are pushed or at position 0.
28# Thus to get position 0 of the debugged program we need to ignore leading
29# any debugger frames.
30typeset -i _Dbg_stack_size
31
32# Where are we in stack? This can be changed by "up", "down" or
33# "frame" commands. 0 means the most recent stack frame with respect
34# to the debugged program and _Dbg_stack_size is the
35# least-recent. Note that inside the debugger the stack is still
36# updated.  On debugger entry, the value is set to 0
37typeset -i  _Dbg_stack_pos
38
39# Save the last-entered frame for to determine stopping when
40# "set force" or step+ is in effect.
41typeset _Dbg_frame_last_filename=''
42typeset -i _Dbg_frame_last_lineno=0
43
44#======================== FUNCTIONS  ============================#
45
46function _Dbg_frame_adjust {
47  (($# != 2)) && return 255
48
49  typeset -i count=$1
50  typeset -i signum=$2
51
52  typeset -i retval
53  _Dbg_frame_int_setup $count || return 2
54
55  typeset -i pos
56  if (( signum==0 )) ; then
57      if (( count < 0 )) ; then
58	  ((pos = _Dbg_stack_size + count - 1))
59      else
60	  ((pos = count))
61      fi
62  else
63    ((pos=_Dbg_stack_pos+(count*signum)))
64  fi
65
66  if (( pos < 0 )) ; then
67    _Dbg_errmsg 'Would be beyond bottom-most (most recent) entry.'
68    return 1
69
70  elif (( pos >= _Dbg_stack_size - 1 )) ; then
71    _Dbg_errmsg 'Would be beyond top-most (least recent) entry.'
72    return 1
73  fi
74
75  typeset -i adjusted_pos
76  adjusted_pos=$(_Dbg_frame_adjusted_pos $pos)
77  _Dbg_stack_pos=$pos
78
79  ## DEBUG
80  ## typeset -p pos
81  ## typeset -p adjusted_pos
82  ## typeset -p BASH_LINENO
83  ## typeset -p BASH_SOURCE
84
85  _Dbg_listline="${BASH_LINENO[adjusted_pos-1]}"
86  # _Dbg_frame_last_lineno="$_Dbg_listline"
87  _Dbg_frame_last_filename="${BASH_SOURCE[adjusted_pos]}"
88  typeset filename; filename="$(_Dbg_file_canonic "$_Dbg_frame_last_filename")"
89  _Dbg_frame_print '->' $_Dbg_stack_pos '' "$filename" $_Dbg_listline ''
90  _Dbg_print_location_and_command "$_Dbg_listline"
91  return 0
92}
93
94# Set $_Dbg_frame_filename to be frame file for the call stack at
95# given position $1 or _Dbg_stack_pos if $1 is omitted. If $2 is
96# given, it indicates if we want the basename only. Otherwise the
97# $_Dbg_set_basename setting is used.  0 is returned if no error,
98# nonzero means some sort of error.
99_Dbg_frame_file() {
100    (($# > 2)) && return 2
101    # FIXME check to see that $1 doesn't run off the end.
102    typeset -i pos=${1:-$_Dbg_stack_pos}
103    typeset -i basename_only=${2:-$_Dbg_set_basename}
104    _Dbg_frame_filename=${BASH_SOURCE[pos]}
105    (( basename_only )) && _Dbg_frame_filename=${_Dbg_frame_filename##*/}
106    return 0
107}
108
109# Set $_Dbg_frame_filename to be frame line for the call stack at
110# given position $1 or _Dbg_stack_pos if $1 is omitted. 0 is returned
111# if no error, nonzero means some sort of error.
112_Dbg_frame_line() {
113    (($# > 1)) && return 2
114    # FIXME check to see that $1 doesn't run off the end.
115    typeset -i pos=${1:-$_Dbg_stack_pos}
116    _Dbg_frame_last_lineno="${BASH_LINENO[pos]}"
117    return 0
118}
119
120# Tests for a signed integer parameter and set global retval
121# if everything is okay. Retval is set to 1 on error
122_Dbg_frame_int_setup() {
123
124  _Dbg_not_running && return 1
125  eval "$_seteglob"
126  if [[ $1 != '' && $1 != $_Dbg_signed_int_pat ]] ; then
127      _Dbg_errmsg "Bad integer parameter: $1"
128      eval "$_resteglob"
129      return 1
130  fi
131  eval "$_resteglob"
132  return 0
133}
134
135# Turn position $1 which uses 0 to represent the most-recent stack entry
136# into which may have additional internal debugger frames pushed on.
137function _Dbg_frame_adjusted_pos
138{
139    if (($# != 1)) ; then
140	echo -n '-1'
141	return 1
142    fi
143    typeset -i pos
144    ((pos=${#FUNCNAME[@]} - _Dbg_stack_size + $1))
145    echo -n $pos
146    return 0
147}
148
149# Creates a parameter string for return in non-local variable
150# _Dbg_parm_str. This is obtained from BASH_ARGC and BASH_ARGV.  On
151# entry, _Dbg_next_argc, and _Dbg_next_argv should be set. These
152# variables and _Dbg_parm_str are updated on exit.  _Dbg_next_argc is
153# and integer index into BASH_ARGC and _Dbg_next_argv is and index
154# into BASH_ARGV. On return
155_Dbg_frame_fn_param_str() {
156    (($# == 0)) || return 1
157    _Dbg_is_int "$_Dbg_next_argc" || return 2
158    _Dbg_is_int "$_Dbg_next_argv" || return 3
159
160    # add 1 to argument count to compensate for this call (of zero
161    # parameters) and at the same time we update _Dbg_next_argc for the
162    # next call.
163    #
164    ((_Dbg_next_argc++))
165    typeset -i arg_count=BASH_ARGC[$_Dbg_next_argc]
166    if ((arg_count == 0)) ; then
167	_Dbg_parm_str=''
168    else
169	typeset -i i
170	_Dbg_parm_str="\"${BASH_ARGV[$_Dbg_next_argv+arg_count-1]}\""
171	for (( i=1; i <= arg_count-1; i++ )) ; do
172	    _Dbg_parm_str+=", \"${BASH_ARGV[$_Dbg_next_argv+arg_count-i-1]}\""
173	done
174	((_Dbg_next_argv+=arg_count))
175    fi
176    return 0
177}
178
179_Dbg_frame_set_fn_param() {
180    (($# == 1)) || return 1
181    typeset -i skip_count=$1
182    # Set to ignore this call in computation
183    _Dbg_next_argc=1
184    _Dbg_next_argv=1
185
186    typeset -i i
187    for (( i=1; i <= skip_count; i++ )) ; do
188	typeset -i arg_count=${BASH_ARGC[$i]}
189	((_Dbg_next_argv+=arg_count))
190    done
191    # After this function returns argv will be one greater. So adjust
192    # for that now.
193    ((_Dbg_next_argc=skip_count))
194    ((_Dbg_next_argv--))
195
196    ## Debug:
197    ## typeset -p BASH_ARGC
198    ## typeset -p BASH_ARGV
199    ## typeset -p FUNCNAME
200    ## typeset -p _Dbg_next_argc
201    ## typeset -p _Dbg_next_argv
202}
203
204# Print "##" or "->" depending on whether or not $1 (POS) is a number
205# between 0 and _Dbg_stack_size-1. For POS, 0 is the top-most
206# (newest) entry. For _Dbg_stack_pos, 0 is the bottom-most entry.
207# 0 is returnd on success, nonzero on failure.
208function _Dbg_frame_prefix {
209    typeset    prefix='??'
210    typeset -i rc=0
211    if (($# == 1)) ; then
212	typeset -i pos=$1
213	if ((pos < 0)) ; then
214	    rc=2
215	elif ((pos >= _Dbg_stack_size)) ; then
216	    rc=3
217	elif (( pos == _Dbg_stack_pos )) ; then
218	    prefix='->'
219	else
220	    prefix='##'
221	fi
222    else
223	rc=1
224    fi
225    echo -n $prefix
226    return $rc
227}
228
229# Print one line in a call stack
230function _Dbg_frame_print {
231    typeset prefix=$1
232    typeset -i pos=$2
233    typeset fn=$3
234    typeset filename="$4"
235    typeset -i line=$5
236    typeset args="$6"
237    typeset callstr=$fn
238    [[ -n $args ]] && callstr="$callstr($args)"
239    _Dbg_msg "$prefix$pos in file \`$filename' at line $line"
240}
241