# -*- 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 setvar _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; setvar _Dbg_brkpt_line = ''() # Number of breakpoints. typeset -i _Dbg_brkpt_count=0 # filename of breakpoint $i typeset -a _Dbg_brkpt_file; setvar _Dbg_brkpt_file = ''() # 1/0 if enabled or not typeset -a _Dbg_brkpt_enable; setvar _Dbg_brkpt_enable = ''() # Number of times hit typeset -a _Dbg_brkpt_counts; setvar _Dbg_brkpt_counts = ''() # Is this a onetime break? typeset -a _Dbg_brkpt_onetime; setvar _Dbg_brkpt_onetime = ''() # Condition to eval true in order to stop. typeset -a _Dbg_brkpt_cond; setvar _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; setvar _Dbg_brkpt_file2linenos = ''() # Maps a resolved filename to a list of breakpoint entries. typeset -A _Dbg_brkpt_file2brkpt; setvar _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 (($# <= 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="$[join(ARGV)]" typeset i eval $_seteglob for i in $to_go { case (i) { $int_pat { _Dbg_enable_disable_display $on $en_dis $i } * { _Dbg_errmsg "Invalid entry number skipped: $i" } } } eval $_resteglob return 0 } elif [[ $1 == 'action' ]] { shift typeset to_go="$[join(ARGV)]" typeset i eval $_seteglob for i in $to_go { case (i) { $int_pat { _Dbg_enable_disable_action $on $en_dis $i } * { _Dbg_errmsg "Invalid entry number skipped: $i" } } } eval $_resteglob return 0 } typeset to_go; setvar to_go = "@ARGV" typeset i eval $_seteglob for i in $to_go { case (i) { $_Dbg_watch_pat { _Dbg_enable_disable_watch $on $en_dis ${del:0:${#del}-1} } $int_pat { _Dbg_enable_disable_brkpt $on $en_dis $i } * { _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; setvar i = "$1" if (( _Dbg_brkpt_counts[i] != 0 )) { if (( _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 { (( $# < 3 || $# > 4 )) && return 1 typeset source_file setvar 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 ((_Dbg_brkpt_max++)) ((_Dbg_brkpt_count++)) setvar _Dbg_brkpt_line[$_Dbg_brkpt_max]=$lineno setvar _Dbg_brkpt_file[$_Dbg_brkpt_max]="$source_file" setvar _Dbg_brkpt_cond[$_Dbg_brkpt_max]="$condition" setvar _Dbg_brkpt_onetime[$_Dbg_brkpt_max]=$is_temp setvar _Dbg_brkpt_counts[$_Dbg_brkpt_max]=0 setvar _Dbg_brkpt_enable[$_Dbg_brkpt_max]=1 typeset dq_source_file setvar dq_source_file = $(_Dbg_esc_dq "$source_file") typeset dq_condition=$(_Dbg_esc_dq "$condition") # Make sure we are not skipping over functions. setvar _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 \"" setvar source_file = $(_Dbg_adjust_filename "$source_file") if (( 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 { (( $# != 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]" ((_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 { (( $# != 2 )) && return 0 typeset filename=$1 typeset -i lineno=$2 typeset -i found=0 typeset fullname setvar 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 (( found == 0 )) { setvar 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 { (( $# == 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 setvar brkpt_nos = ''(${_Dbg_brkpt_file2brkpt[$source_file]}) for try in ${_Dbg_brkpt_file2linenos[$source_file]} { ((i++)) if (( brkpt_nos[i] == del )) { if (( try != lineno )) { _Dbg_errmsg 'internal brkpt structure inconsistency' return 1 } _Dbg_unset_brkpt_arrays $del ((found++)) } else { setvar new_lineno_val = "" $try "" setvar new_brkpt_nos = "" ${brkpt_nos[$i]} "" } } if (( found > 0 )) { if (( ${#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 { (($# != 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 { (($# != 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]}\") == "" ]] { setvar new_val = '''' } elif (( _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 (( $# < 1 )) { typeset _Dbg_prompt_output=${_Dbg_tty:-/dev/null} read $_Dbg_edit -p "Delete all watchpoints? (y/n): " \ <&$_Dbg_input_desc 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 }