# -*- shell-script -*- # Signal handling routines # # Copyright (C) 2002, 2003, 2004, 2006, 2007, 2008, 2010, 2011 # 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. # ==================== VARIABLES ======================================= # The "set" options in effect ($-) before debugger was invoked. typeset _Dbg_old_setopts # Place to save debugged program's exit handler, if any. typeset _Dbg_old_EXIT_handler='' typeset -i _Dbg_QUIT_ON_QUIT=0 # Return code that debugged program reports typeset -i _Dbg_program_exit_code=0 ############################################################ ## Signal arrays: These are indexed by the signal number. ## ############################################################ # Should we print that a signal was intercepted? # Each entry is "print" or "noprint" or null. typeset -a _Dbg_sig_print; setglobal _Dbg_sig_print = ''() # Should we reentry the debugger command loop on receiving the signal? # Each entry is "stop" or "nostop" or null. typeset -a _Dbg_sig_stop; setglobal _Dbg_sig_stop = ''() # Should we show a traceback on receiving the signal? # Each entry is "stack" or "nostack" or null. typeset -a _Dbg_sig_show_stack; setglobal _Dbg_sig_show_stack = ''() # Should pass the signal to the user program?? # Each entry is the trap handler with some variables substituted. typeset -a _Dbg_sig_passthrough; setglobal _Dbg_sig_passthrough = ''() # Should pass the signal to the user program?? # Each entry is the trap handler with some variables substituted. typeset -i _Dbg_return_level=0 # Place to save values of $1, $2, etc. typeset -a _Dbg_arg; setglobal _Dbg_arg = ''() # Save value of handler variable _Dbg_old_$sig proc _Dbg_save_handler { typeset -r sig=$1 typeset old_handler='' setglobal old_handler_test = $[trap -p $sig] if [[ -n $old_handler ]] { typeset -a old_hand_a setglobal old_hand_a = ''($old_handler) setglobal old_handler = $[_Dbg_subst_handler_var $(old_hand_a[2])] typeset -r decl_cmd="typeset -r _Dbg_old_$(sig)_handler='$old_handler'" eval $decl_cmd } } # Adjust handler variables to take into account the fact that when we # call the handler we will have added another call before the user's # handler. proc _Dbg_subst_handler_var { typeset -i i typeset result='' for arg in [$ifsjoin(Argv)] { match $arg { with '$LINENO' setglobal arg = ''${BASH_LINENO[0]}'' with '${BASH_SOURCE[0]}' setglobal arg = ''${BASH_SOURCE[1]}'' with '${FUNCNAME[0]}' setglobal arg = ''${FUNCNAME[1]}'' with '${BASH_LINENO[0]}' setglobal arg = ''${BASH_LINENO[1]}'' } if [[ $result == '' ]] { setglobal result = $arg } else { setglobal result = ""$result $arg"" } } echo $result } # Debugger exit handler. Don't really exit - but go back the debugger # command loop proc _Dbg_exit_handler { # Consider putting the following line(s) in a routine. # Ditto for the restore environment typeset -i _Dbg_debugged_exit_code=$Status # Turn off line and variable trace listing; allow unset parameter expansion. set +x +v +u if [[ ${_Dbg_sig_print[0]} == "print" ]] { # Note: use the same message that gdb does for this. _Dbg_msg "Program received signal EXIT (0)..." if [[ ${_Dbg_sig_show_stack[0]} == "showstack" ]] { _Dbg_do_backtrace 0 } } if [[ $_Dbg_old_EXIT_handler != '' ]] { eval $_Dbg_old_EXIT_handler } # If we've set the QUIT signal handler not to stop, or we've in the # middle of leaving so many (subshell) levels or we have set to # leave quietly on termination, then do it! if [[ ${_Dbg_sig_stop[0]} != "stop" ]] \ || sh-expr ' _Dbg_QUIT_LEVELS != 0 ' \ || sh-expr ' _Dbg_QUIT_ON_QUIT ' { _Dbg_do_quit # We don't return from here. } # We've tested for all the quitting conditions above. # Even though this is an exit handler, don't exit! typeset term_msg="normally" if [[ $_Dbg_debugged_exit_code != 0 ]] { setglobal term_msg = ""with code $_Dbg_debugged_exit_code"" } # If we tried to exit from inside a subshell, we only want to enter # the command loop if don't have any pending subshells. if sh-expr ' BASH_SUBSHELL == 0 ' { _Dbg_msg \ "Debugged program terminated $term_msg. Use q to quit or R to restart." setglobal _Dbg_running = '0' while : { _Dbg_process_commands } } } # Generic signal handler. We consult global array _Dbg_sig_* for how # to handle this signal. # Since the command loop may be called we need to be careful about # using variable names that would be exposed to the user. proc _Dbg_sig_handler { # Consider putting the following line(s) in a routine. # Ditto for the restore environment typeset -i _Dbg_debugged_exit_code=$Status setglobal _Dbg_old_set_opts = $Flags # Turn off line and variable trace listing if were not in our own debug # mode, and set our own PS4 for debugging inside the debugger sh-expr ' !_Dbg_set_debug ' && set +x +v +u shopt -s extdebug # This is the signal number. E.g. 15 is SIGTERM typeset -r -i _Dbg_signum=$1 if [[ ${_Dbg_sig_print[$_Dbg_signum]} == "print" ]] || \ [[ ${_Dbg_sig_stop[$_Dbg_signum]} == "stop" ]] { typeset -r name=$[_Dbg_signum2name $_Dbg_signum] # Note: use the same message that gdb does for this. _Dbg_msg "Program received signal $name ($_Dbg_signum)..." if [[ ${_Dbg_sig_show_stack[$_Dbg_signum]} == "showstack" ]] { setglobal _Dbg_stack_pos = '0' sh-expr '_Dbg_stack_size = ${#FUNCNAME[@]}' _Dbg_do_backtrace } } if [[ ${_Dbg_sig_stop[$_Dbg_signum]} == "stop" ]] { ### The below duplicates what is above in _Dbg_debug_trap handler ### Should put common stuff into a function. shift # signum _Dbg_save_args @Argv _Dbg_set_debugger_entry _Dbg_hook_enter_debugger 'on receiving a signal' 'noprint' return $_Dbg_continue_rc } elif sh-expr ' _Dbg_sig_old_handler[_Dbg_signum] ' { eval $(_Dbg_sig_old_handler[$_Dbg_signum]) } _Dbg_set_to_return_from_debugger 1 return $_Dbg_debugged_exit_code } proc _Dbg_err_handler { if [[ $_Dbg_old_ERR_handler != '' ]] { eval $_Dbg_old_ERR_handler } _Dbg_msg "Error occured at $(BASH_SOURCE[1]):$(BASH_LINENO[1])" _Dbg_process_commands } # Echo the name for a given signal number $1. The resulting name # is in _Dbg_return proc _Dbg_signum2name { typeset -i -r signum=$1; builtin kill -l $signum !2 >/dev/null return $? } # Return the signal number for a given signal name $1. The resulting number # is in _Dbg_return proc _Dbg_name2signum { typeset -r signame=$1; builtin kill -l $signame !2 >/dev/null return $? } proc _Dbg_subexit_handler { # Read in the journal to pick up variable settings that might have # been left from a subshell. if [[ ${FUNCNAME[1]} == _Dbg_* ]] && sh-expr ' !_Dbg_set_debug ' { return 0 } _Dbg_source_journal if sh-expr ' _Dbg_QUIT_LEVELS > 0 ' { _Dbg_do_quit $_Dbg_debugged_exit_code } } # Set up generic trap handler. Arguments are: # NAME print showstack stop passthrough proc _Dbg_init_trap { typeset -r name=$1 typeset -i -r signum=$[_Dbg_name2signum $name] compat array-assign _Dbg_sig_print '$signum' $2; compat array-assign _Dbg_sig_show_stack '$signum' $3; compat array-assign _Dbg_sig_stop '$signum' $4; # Work out passthrough later... # if [[ $5 == "pass*" ]] ; then # get existing trap from env. # _Dbg_sig_show_passthrough[$signum]=....; # if sh-expr ' signum == 0 ' { trap '_Dbg_exit_handler "$BASH_COMMAND"' EXIT } else { typeset trap_cmd="trap '_Dbg_sig_handler $signum \"\$BASH_COMMAND\" \"\$@\"' $name" eval $trap_cmd } } proc _Dbg_init_default_traps { _Dbg_init_trap EXIT "noprint" "nostack" "stop" _Dbg_init_trap ILL "print" "showstack" "stop" _Dbg_init_trap INT "print" "showstack" "stop" _Dbg_init_trap QUIT "print" "showstack" "stop" _Dbg_init_trap TERM "print" "showstack" "stop" _Dbg_init_trap TRAP "print" "showstack" "stop" }