1# bashdb.fns - Bourne-Again Shell Debugger functions
2
3_BUFSIZ=100
4
5# Here after each statement in script being debugged.
6# Handle single-step and breakpoints.
7_steptrap() {
8	let _curline=$1-1		# no. of line that just ran
9	let "$_curline < 1" && let _curline=1
10
11	let "$_curline > $_firstline+$_BUFSIZ" && _readin $_curline
12
13	let " $_trace" &&
14		_msg "$PS4, line $_curline: ${_lines[$(($_curline-$_firstline+1))]}"
15
16
17	# if in step mode, decrement counter
18	let " $_steps >= 0" && let _steps="$_steps - 1"
19
20	# first check if line num or string brkpt. reached
21	if _at_linenumbp || _at_stringbp; then
22		_msg "Reached breakpoint at line $_curline"
23		_cmdloop		# enter debugger
24
25	# if not, check whether break condition exists and is true
26	elif [ -n "$_brcond" ] && eval $_brcond; then
27		_msg "Break condition $_brcond true at line $_curline"
28		_cmdloop		# enter debugger
29
30	# next, check if step mode and no. of steps is up
31	elif let "$_steps == 0"; then
32		_msg "Stopped at line $_curline"
33		_cmdloop		# enter debugger
34	fi
35}
36
37
38# Debugger command loop.
39# Here at start of debugger session, when brkpt. reached, or after single-step.
40_cmdloop() {
41	local cmd args
42
43# added support for default command (last one entered)
44
45	while read -e -p "bashdb> [$lastcmd $lastargs] " cmd args; do
46		if [ -z "$cmd" ]; then
47			cmd=$lastcmd
48			args=$lastargs 
49		fi
50
51		lastcmd="$cmd"
52		lastargs=$args
53
54# made commands to be debugger commands by default, no need for '*' prefix
55
56		case $cmd in
57		bp ) _setbp $args ;;	#set brkpt at line num or string
58
59		bc ) _setbc $args ;;	# set break condition
60
61		cb ) _clearbp ;;	# clear all brkpts.
62
63		g ) return ;;		# start/resume execution
64
65		s ) let _steps=${args:-1} 
66		    return ;;		# single-step N times(default 1)
67
68		x ) _xtrace ;;	# toggle execution trace
69
70		pr ) _print $args ;;	# print lines in file
71
72		\? | h | help ) _menu ;; # print command menu
73
74		hi ) history ;;		# show command history
75
76		q ) _cleanup; exit ;;		# quit
77
78		\! ) eval $args ;;	# run shell command
79
80		* ) _msg "Invalid command: $cmd" ; _menu ;;
81		esac
82	done
83}
84
85
86# see if next line no. is a brkpt.
87_at_linenumbp() {
88	if [ -z "${_linebp}" ]; then
89		return 1
90	fi
91	echo "${_curline}" | grep -E "(${_linebp%\|})" >/dev/null 2>&1
92	return $?
93}
94
95
96# search string brkpts to see if next line in script matches.
97_at_stringbp() {
98	local l;
99
100	if [ -z "$_stringbp" ]; then
101		return 1;
102	fi
103	l=${_lines[$_curline-$_firstline+1]}
104	echo "${l}" | grep -E "\\*(${_stringbp%\|})\\*" >/dev/null 2>&1
105	return $?
106}
107
108
109# print message to stderr
110_msg() {
111	echo -e "$@" >&2
112}
113
114
115# set brkpt(s) at given line numbers and/or strings
116# by appending lines to brkpt file
117_setbp() {
118	declare -i n
119	case "$1" in
120	"")	_listbp ;;
121	[0-9]*)	#number, set brkpt at that line
122		n=$1
123		_linebp="${_linebp}$n|"
124		_msg "Breakpoint at line " $1
125		;;
126	*)	#string, set brkpt at next line w/string
127		_stringbp="${_stringbp}$@|"
128		_msg "Breakpoint at next line containing $@."
129		;;
130	esac
131}
132
133
134# list brkpts and break condition.
135_listbp() {
136	_msg "Breakpoints at lines:"
137	_msg "${_linebp//\|/ }"
138	_msg "Breakpoints at strings:"
139	_msg "${_stringbp//\|/ }"
140	_msg "Break on condition:"
141	_msg "$_brcond"
142}
143
144
145# set or clear break condition
146_setbc() {
147	if [ -n "$@" ] ; then
148		_brcond=$args
149		_msg "Break when true: $_brcond"
150	else
151		_brcond=
152		_msg "Break condition cleared"
153	fi
154}
155
156
157# clear all brkpts
158_clearbp() {
159	_linebp=
160	_stringbp=
161	_msg "All breakpoints cleared"
162}
163
164
165# toggle execution trace feature
166_xtrace() {
167	let _trace="! $_trace"
168
169	_msg "Execution trace \c"
170	let " $_trace" && _msg "on." || _msg "off."
171}
172
173
174# print command menu
175_menu() {
176
177# made commands to be debugger commands by default, no need for '*' prefix
178
179	_msg 'bashdb commands:
180	bp N			set breakpoint at line N
181	bp string		set breakpoint at next line containing "string"
182	bp			list breakpoints and break condition
183	bc string		set break condition to "string"
184	bc			clear break condition
185	cb			clear all breakpoints
186	g			start/resume execution
187	s [N]			execute N statements (default 1)
188	x			toggle execution trace on/off (default on)
189	pr [start|.] [cnt]	print "cnt" lines from line no. "start"
190	?, h, help		print this menu
191	hi			show command history	
192	q			quit
193
194	! cmd [args]		execute command "cmd" with "args"
195
196	default:		last command (in "[ ]" at the prompt)
197
198	Readline command line editing (emacs/vi mode) is available'
199}
200
201
202# erase temp files before exiting
203_cleanup() {
204	rm $_dbgfile 2>/dev/null
205}
206
207
208# read $_BUFSIZ lines from $_guineapig into _lines array, starting from line $1
209# save number of first line read in _firstline
210_readin() {
211	declare -i _i=1			
212	let _firstline=$1
213
214	SEDCMD="$_firstline,$(($_firstline+$_BUFSIZ))p"
215
216	sed -n "$SEDCMD" $_guineapig > /tmp/_script.$$
217	while read -r _lines[$_i]; do
218		_i=_i+1
219	done  < /tmp/_script.$$
220	rm -f /tmp/_script.$$ 2>/dev/null
221}
222
223_print() {
224	typeset _start _cnt
225
226	if [ -z "$1" ] || [ "$1" = . ]; then
227		_start=$_curline
228	else
229		_start=$1
230	fi
231
232	_cnt=${2:-9}
233
234	SEDCMD="$_start,$(($_start+$_cnt))p"
235
236	pr -tn $_guineapig | sed -n "$SEDCMD"
237}
238