# -*- shell-script -*- # break.sh - Debugger Break and Watch routines # # Copyright (C) 2002-2003, 2006-2011, 2014-2016 Rocky Bernstein # # # 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, 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; see the file COPYING. If not, write to # the Free Software Foundation, 59 Temple Place, Suite 330, Boston, # MA 02111 USA. #================ VARIABLE INITIALIZATIONS ====================# typeset -a _Dbg_keep setglobal _Dbg_keep = ''('keep' 'del') # Note: we loop over possibly sparse arrays with _Dbg_brkpt_max by adding one # and testing for an entry. Could add yet another array to list only # used indices. Bash is kind of primitive. # Breakpoint data structures # Line number of breakpoint $i typeset -a _Dbg_brkpt_line; setglobal _Dbg_brkpt_line = ''() # Number of breakpoints. typeset -i _Dbg_brkpt_count=0 # filename of breakpoint $i typeset -a _Dbg_brkpt_file; setglobal _Dbg_brkpt_file = ''() # 1/0 if enabled or not typeset -a _Dbg_brkpt_enable; setglobal _Dbg_brkpt_enable = ''() # Number of times hit typeset -a _Dbg_brkpt_counts; setglobal _Dbg_brkpt_counts = ''() # Is this a onetime break? typeset -a _Dbg_brkpt_onetime; setglobal _Dbg_brkpt_onetime = ''() # Condition to eval true in order to stop. typeset -a _Dbg_brkpt_cond; setglobal _Dbg_brkpt_cond = ''() # Needed because we can't figure out what the max index is and arrays # can be sparse. typeset -i _Dbg_brkpt_max=0 # Maps a resolved filename to a list of beakpoint line numbers in that file typeset -A _Dbg_brkpt_file2linenos; setglobal _Dbg_brkpt_file2linenos = ''() # Maps a resolved filename to a list of breakpoint entries. typeset -A _Dbg_brkpt_file2brkpt; setglobal _Dbg_brkpt_file2brkpt = ''() # Note: we loop over possibly sparse arrays with _Dbg_brkpt_max by adding one # and testing for an entry. Could add yet another array to list only # used indices. Bash is kind of primitive. # Watchpoint data structures typeset -a _Dbg_watch_exp=() # Watchpoint expressions typeset -a _Dbg_watch_val=() # values of watchpoint expressions typeset -ai _Dbg_watch_arith=() # 1 if arithmetic expression or not. typeset -ai _Dbg_watch_count=() # Number of times hit typeset -ai _Dbg_watch_enable=() # 1/0 if enabled or not typeset -i _Dbg_watch_max=0 # Needed because we can't figure out what # the max index is and arrays can be sparse typeset _Dbg_watch_pat="$(int_pat)[wW]" #========================= FUNCTIONS ============================# proc _Dbg_save_breakpoints { typeset file typeset -p _Dbg_brkpt_line >> $_Dbg_statefile typeset -p _Dbg_brkpt_file >> $_Dbg_statefile typeset -p _Dbg_brkpt_cond >> $_Dbg_statefile typeset -p _Dbg_brkpt_count >> $_Dbg_statefile typeset -p _Dbg_brkpt_enable >> $_Dbg_statefile typeset -p _Dbg_brkpt_onetime >> $_Dbg_statefile typeset -p _Dbg_brkpt_max >> $_Dbg_statefile typeset -p _Dbg_brkpt_file2linenos >> $_Dbg_statefile typeset -p _Dbg_brkpt_file2brkpt >> $_Dbg_statefile } proc _Dbg_save_watchpoints { typeset -p _Dbg_watch_exp >> $_Dbg_statefile typeset -p _Dbg_watch_val >> $_Dbg_statefile typeset -p _Dbg_watch_arith >> $_Dbg_statefile typeset -p _Dbg_watch_count >> $_Dbg_statefile typeset -p _Dbg_watch_enable >> $_Dbg_statefile typeset -p _Dbg_watch_max >> $_Dbg_statefile } # Start out with general break/watchpoint functions first... # Enable/disable breakpoint or watchpoint by entry numbers. proc _Dbg_enable_disable { if sh-expr '$# <= 2' { _Dbg_errmsg "_Dbg_enable_disable error - need at least 2 args, got $Argc" return 1 } typeset -i on=$1 typeset en_dis=$2 shift; shift if [[ $1 == 'display' ]] { shift typeset to_go="$ifsjoin(Argv)" typeset i eval $_seteglob for i in [$to_go] { match $i { with $int_pat _Dbg_enable_disable_display $on $en_dis $i with * _Dbg_errmsg "Invalid entry number skipped: $i" } } eval $_resteglob return 0 } elif [[ $1 == 'action' ]] { shift typeset to_go="$ifsjoin(Argv)" typeset i eval $_seteglob for i in [$to_go] { match $i { with $int_pat _Dbg_enable_disable_action $on $en_dis $i with * _Dbg_errmsg "Invalid entry number skipped: $i" } } eval $_resteglob return 0 } typeset to_go; setglobal to_go = @Argv typeset i eval $_seteglob for i in [$to_go] { match $i { with $_Dbg_watch_pat _Dbg_enable_disable_watch $on $en_dis $(del:0:${#del}-1) with $int_pat _Dbg_enable_disable_brkpt $on $en_dis $i with * _Dbg_errmsg "Invalid entry number skipped: $i" } } eval $_resteglob return 0 } # Print a message regarding how many times we've encountered # breakpoint number $1 if the number of times is greater than 0. # Uses global array _Dbg_brkpt_counts. proc _Dbg_print_brkpt_count { typeset -i i; setglobal i = $1 if sh-expr ' _Dbg_brkpt_counts[i] != 0 ' { if sh-expr ' _Dbg_brkpt_counts[i] == 1 ' { _Dbg_printf "\tbreakpoint already hit 1 time" } else { _Dbg_printf "\tbreakpoint already hit %d times" $(_Dbg_brkpt_counts[$i]) } } } #======================== BREAKPOINTS ============================# # clear all brkpts proc _Dbg_clear_all_brkpt { _Dbg_write_journal_eval "_Dbg_brkpt_file2linenos=()" _Dbg_write_journal_eval "_Dbg_brkpt_file2brkpt=()" _Dbg_write_journal_eval "_Dbg_brkpt_line=()" _Dbg_write_journal_eval "_Dbg_brkpt_cond=()" _Dbg_write_journal_eval "_Dbg_brkpt_file=()" _Dbg_write_journal_eval "_Dbg_brkpt_enable=()" _Dbg_write_journal_eval "_Dbg_brkpt_counts=()" _Dbg_write_journal_eval "_Dbg_brkpt_onetime=()" _Dbg_write_journal_eval "_Dbg_brkpt_count=0" } # Internal routine to a set breakpoint unconditonally. proc _Dbg_set_brkpt { sh-expr ' $# < 3 || $# > 4 ' && return 1 typeset source_file setglobal source_file = $[_Dbg_expand_filename $1] typeset -ri lineno=$2 typeset -ri is_temp=$3 typeset -r condition=$(4:-1) # Increment brkpt_max here because we are 1-origin sh-expr '_Dbg_brkpt_max++' sh-expr '_Dbg_brkpt_count++' compat array-assign _Dbg_brkpt_line '$_Dbg_brkpt_max' $lineno compat array-assign _Dbg_brkpt_file '$_Dbg_brkpt_max' $source_file compat array-assign _Dbg_brkpt_cond '$_Dbg_brkpt_max' $condition compat array-assign _Dbg_brkpt_onetime '$_Dbg_brkpt_max' $is_temp compat array-assign _Dbg_brkpt_counts '$_Dbg_brkpt_max' '0' compat array-assign _Dbg_brkpt_enable '$_Dbg_brkpt_max' '1' typeset dq_source_file setglobal dq_source_file = $[_Dbg_esc_dq $source_file] typeset dq_condition=$[_Dbg_esc_dq $condition] # Make sure we are not skipping over functions. setglobal _Dbg_old_set_opts = ""$_Dbg_old_set_opts -o functrace"" _Dbg_write_journal_eval "_Dbg_old_set_opts='$_Dbg_old_set_opts'" _Dbg_write_journal_eval "_Dbg_brkpt_line[$_Dbg_brkpt_max]=$lineno" _Dbg_write_journal_eval "_Dbg_brkpt_file[$_Dbg_brkpt_max]=\"$dq_source_file\"" _Dbg_write_journal "_Dbg_brkpt_cond[$_Dbg_brkpt_max]=\"$dq_condition\"" _Dbg_write_journal "_Dbg_brkpt_onetime[$_Dbg_brkpt_max]=$is_temp" _Dbg_write_journal "_Dbg_brkpt_counts[$_Dbg_brkpt_max]=\"0\"" _Dbg_write_journal "_Dbg_brkpt_enable[$_Dbg_brkpt_max]=1" # Add line number with a leading and trailing space. Delimiting the # number with space helps do a string search for the line number. _Dbg_write_journal_eval "_Dbg_brkpt_file2linenos[$source_file]+=\" $lineno \"" _Dbg_write_journal_eval "_Dbg_brkpt_file2brkpt[$source_file]+=\" $_Dbg_brkpt_max \"" setglobal source_file = $[_Dbg_adjust_filename $source_file] if sh-expr ' is_temp == 0 ' { _Dbg_msg "Breakpoint $_Dbg_brkpt_max set in file $(source_file), line $lineno." } else { _Dbg_msg "One-time breakpoint $_Dbg_brkpt_max set in file $(source_file), line $lineno." } _Dbg_write_journal "_Dbg_brkpt_max=$_Dbg_brkpt_max" return 0 } # Internal routine to unset the actual breakpoint arrays. # 0 is returned if successful proc _Dbg_unset_brkpt_arrays { sh-expr ' $# != 1 ' && return 1 typeset -i del=$1 _Dbg_write_journal_eval "unset _Dbg_brkpt_line[$del]" _Dbg_write_journal_eval "unset _Dbg_brkpt_counts[$del]" _Dbg_write_journal_eval "unset _Dbg_brkpt_file[$del]" _Dbg_write_journal_eval "unset _Dbg_brkpt_enable[$del]" _Dbg_write_journal_eval "unset _Dbg_brkpt_cond[$del]" _Dbg_write_journal_eval "unset _Dbg_brkpt_onetime[$del]" sh-expr '_Dbg_brkpt_count--' return 0 } # Internal routine to delete a breakpoint by file/line. # We return the number of breakpoints found or zero if we didn't find # a breakpoint proc _Dbg_unset_brkpt { sh-expr ' $# != 2 ' && return 0 typeset filename=$1 typeset -i lineno=$2 typeset -i found=0 typeset fullname setglobal fullname = $[_Dbg_expand_filename $filename] # FIXME: combine with _Dbg_hook_breakpoint_hit typeset -a linenos eval "linenos=($(_Dbg_brkpt_file2linenos[$fullname]))" typeset -a brkpt_nos eval "brkpt_nos=($(_Dbg_brkpt_file2brkpt[$fullname]))" typeset -i i # Note: <= rather than < looks funny below, but that is correct. for ((i=0; i <= ${#linenos[@]}; i++)); do if (( linenos[i] == lineno )) ; then # Got a match, find breakpoint entry number typeset -i brkpt_num (( brkpt_num = brkpt_nos[i] )) _Dbg_unset_brkpt_arrays $brkpt_num unset linenos[i] _Dbg_brkpt_file2linenos[$fullname]=${linenos[@]} typeset -a brkpt_nos eval "brkpt_nos=(${_Dbg_brkpt_file2brkpt[$filename]})" unset brkpt_nos[$i] _Dbg_brkpt_file2brkpt[$filename]=${brkpt_nos[@]} (( found ++ )) fi done if sh-expr ' found == 0 ' { setglobal filename = $[_Dbg_file_canonic $filename] _Dbg_errmsg "No breakpoint found at $filename, line $(lineno)." } return $found } # Routine to a delete breakpoint by entry number: $1. # Returns whether or not anything was deleted. proc _Dbg_delete_brkpt_entry { sh-expr ' $# == 0 ' && return 0 typeset -r del="$1" typeset -i i typeset -i found=0 if [[ -z ${_Dbg_brkpt_file[$del]} ]] { _Dbg_errmsg "No breakpoint number $del." return 1 } typeset source_file=$(_Dbg_brkpt_file[$del]) typeset -i lineno=$(_Dbg_brkpt_line[$del]) typeset -i try typeset new_lineno_val='' typeset new_brkpt_nos='' typeset -i i=-1 typeset -a brkpt_nos setglobal brkpt_nos = ''(${_Dbg_brkpt_file2brkpt[$source_file]}) for try in [$(_Dbg_brkpt_file2linenos[$source_file])] { sh-expr 'i++' if sh-expr ' brkpt_nos[i] == del ' { if sh-expr ' try != lineno ' { _Dbg_errmsg 'internal brkpt structure inconsistency' return 1 } _Dbg_unset_brkpt_arrays $del sh-expr 'found++' } else { setglobal new_lineno_val = "" $try "" setglobal new_brkpt_nos = "" $(brkpt_nos[$i]) "" } } if sh-expr ' found > 0 ' { if sh-expr ' ${#new_lineno_val[@]} == 0 ' { # Remove array entirely _Dbg_write_journal_eval "unset '_Dbg_brkpt_file2linenos[$source_file]'" _Dbg_write_journal_eval "unset '_Dbg_brkpt_file2brkpt[$source_file]'" } else { # Replace array entries with reduced set. _Dbg_write_journal_eval "_Dbg_brkpt_file2linenos[$source_file]=\"$(new_lineno_val)\"" _Dbg_write_journal_eval "_Dbg_brkpt_file2brkpt[$source_file]=\"$new_brkpt_nos\"" } return 0 } return 1 } # Enable/disable aciton(s) by entry numbers. proc _Dbg_enable_disable_action { sh-expr '$# != 3' && return 1 typeset -i on=$1 typeset en_dis=$2 typeset -i i=$3 if [[ -n "${_Dbg_brkpt_file[$i]}" ]] { if [[ ${_Dbg_action_enable[$i]} == $on ]] { _Dbg_errmsg "Breakpoint entry $i already $(en_dis), so nothing done." return 1 } else { _Dbg_write_journal_eval "_Dbg_brkpt_enable[$i]=$on" _Dbg_msg "Action entry $i $en_dis." return 0 } } else { _Dbg_errmsg "Action entry $i doesn't exist, so nothing done." return 1 } } # Enable/disable breakpoint(s) by entry numbers. proc _Dbg_enable_disable_brkpt { sh-expr '$# != 3' && return 1 typeset -i on=$1 typeset en_dis=$2 typeset -i i=$3 if [[ -n "${_Dbg_brkpt_file[$i]}" ]] { if [[ ${_Dbg_brkpt_enable[$i]} == $on ]] { _Dbg_errmsg "Breakpoint entry $i already $(en_dis), so nothing done." return 1 } else { _Dbg_write_journal_eval "_Dbg_brkpt_enable[$i]=$on" _Dbg_msg "Breakpoint entry $i $en_dis." return 0 } } else { _Dbg_errmsg "Breakpoint entry $i doesn't exist, so nothing done." return 1 } } #======================== WATCHPOINTS ============================# proc _Dbg_get_watch_exp_eval { typeset -i i=$1 typeset new_val if [[ $(eval echo \"${_Dbg_watch_exp[$i]}\") == "" ]] { setglobal new_val = '''' } elif sh-expr ' _Dbg_watch_arith[$i] == 1 ' { source ${_Dbg_libdir}/dbg-set-d-vars.inc eval let new_val='"'$(_Dbg_watch_exp[$i])'"' } else { source ${_Dbg_libdir}/dbg-set-d-vars.inc eval new_val="$(_Dbg_watch_exp[$i])" } echo $new_val } # Enable/disable watchpoint(s) by entry numbers. proc _Dbg_enable_disable_watch { typeset -i on=$1 typeset en_dis=$2 typeset -i i=$3 if test -n $(_Dbg_watch_exp[$i]) { if [[ ${_Dbg_watch_enable[$i]} == $on ]] { _Dbg_msg "Watchpoint entry $i already $en_dis so nothing done." } else { _Dbg_write_journal_eval "_Dbg_watch_enable[$i]=$on" _Dbg_msg "Watchpoint entry $i $en_dis." } } else { _Dbg_msg "Watchpoint entry $i doesn't exist so nothing done." } } proc _Dbg_list_watch { if test $(#_Dbg_watch_exp[@]) != 0 { typeset i=0 j _Dbg_section "Num Type Enb Expression" for (( i=0; (( i < _Dbg_watch_max )); i++ )) ; do if [ -n "${_Dbg_watch_exp[$i]}" ] ;then _Dbg_printf '%-3d watchpoint %-4s %s' $i \ ${_Dbg_yn[${_Dbg_watch_enable[$i]}]} \ "${_Dbg_watch_exp[$i]}" _Dbg_print_brkpt_count ${_Dbg_watch_count[$i]} fi done } else { _Dbg_msg "No watch expressions have been set." } } proc _Dbg_delete_watch_entry { typeset -i del=$1 if test -n $(_Dbg_watch_exp[$del]) { _Dbg_write_journal_eval "unset _Dbg_watch_exp[$del]" _Dbg_write_journal_eval "unset _Dbg_watch_val[$del]" _Dbg_write_journal_eval "unset _Dbg_watch_enable[$del]" _Dbg_write_journal_eval "unset _Dbg_watch_count[$del]" } else { _Dbg_msg "Watchpoint entry $del doesn't exist so nothing done." } } proc _Dbg_clear_watch { if sh-expr ' $# < 1 ' { typeset _Dbg_prompt_output=$(_Dbg_tty:-/dev/null) read $_Dbg_edit -p "Delete all watchpoints? (y/n): " \ <&$_Dbg_input_desc !2 2>>$_Dbg_prompt_output if [[ $REPLY == [Yy]* ]] { _Dbg_write_journal_eval unset _Dbg_watch_exp[@] _Dbg_write_journal_eval unset _Dbg_watch_val[@] _Dbg_write_journal_eval unset _Dbg_watch_enable[@] _Dbg_write_journal_eval unset _Dbg_watch_count[@] _Dbg_msg "All Watchpoints have been cleared" } return 0 } eval $_seteglob if [[ $1 == $int_pat ]] { _Dbg_write_journal_eval "unset _Dbg_watch_exp[$1]" _msg "Watchpoint $i has been cleared" } else { _Dbg_list_watch _basdhb_msg "Please specify a numeric watchpoint number" } eval $_resteglob }