# -*- shell-script -*- # dbg-processor.sh - Top-level debugger commands # # Copyright (C) 2008-2012, 2015 # 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. # Are we inside the middle of a "skip" command? typeset -i _Dbg_inside_skip=0 # Hooks that get run on each command loop typeset -A _Dbg_cmdloop_hooks setvar _Dbg_cmdloop_hooks['display']='_Dbg_eval_all_display' # Can be bash's "read" builtin or a command-completing "read" builtin # provided by this debugger. typeset _Dbg_read_fn; setvar _Dbg_read_fn = ''read'' # A variable holding a space is set so it can be used in a "set prompt" command # ("read" in the main command loop will remove a trailing space so we need # another way to allow a user to enter spaces in the prompt.) typeset _Dbg_space=' ' # Should we allow editing of debugger commands? # The value should either be '-e' or ''. And if it is # on, the edit style indicates what style edit keystrokes. typeset _Dbg_edit='-e' typeset _Dbg_edit_style='emacs' # or vi set -o $_Dbg_edit_style # What do we use for a debugger prompt? Technically we don't need to # use the above $bashdb_space in the assignment below, but we put it # in to suggest to a user that this is how one gets a spaces into the # prompt. typeset _Dbg_prompt_str='$_Dbg_debugger_name${_Dbg_less}${#_Dbg_history[@]}${_Dbg_greater}$_Dbg_space' # The arguments in the last "x" command. typeset _Dbg_last_x_args='' # The canonical name of last command run. typeset _Dbg_last_cmd='' # A list of debugger command input-file descriptors. # Duplicate standard input typeset -i _Dbg_fdi ; exec {_Dbg_fdi}<&0 typeset -i _Dbg_fd_last=0 # keep a list of source'd command files. If the entry is "" then we are # interactive. typeset -a _Dbg_cmdfile; setvar _Dbg_cmdfile = ''('') # A list of debugger command input-file descriptors. typeset -a _Dbg_fd; setvar _Dbg_fd = ''($_Dbg_fdi) typeset _Dbg_prompt_output # ===================== FUNCTIONS ======================================= # The main debugger command reading loop. # # Note: We have to be careful here in naming "local" variables. In contrast # to other places in the debugger, because of the read/eval loop, they are # in fact seen by those using the debugger. So in contrast to other "local"s # in the debugger, we prefer to preface these with _Dbg_. proc _Dbg_process_commands { # THIS SHOULD BE DONE IN dbg-sig.sh, but there's a bug in BASH in # trying to change "trap RETURN" inside a "trap RETURN" handler.... # Turn off return trapping. Not strictly necessary, since it *should* be # covered by the _Dbg_ test below if we've named functions correctly. # However turning off the RETURN trap should reduce unnecessary # trap RETURN calls. setvar _Dbg_inside_skip = '0' setvar _Dbg_step_ignore = '-1' # Nuke any prior step ignore counts setvar _Dbg_continue_rc = '-1' # Don't continue exectuion unless told to do so. _Dbg_write_journal "_Dbg_step_ignore=$_Dbg_step_ignore" typeset key # Evaluate all hooks for key in ${!_Dbg_cmdloop_hooks[@]} { ${_Dbg_cmdloop_hooks[$key]} } { # Set up prompt to show shell and subshell levels. typeset _Dbg_greater='' typeset _Dbg_less='' typeset result # Used by copies to return a value. if _Dbg_copies '>' $_Dbg_DEBUGGER_LEVEL { setvar _Dbg_greater = "$result" setvar _Dbg_less = ${result//>/<} } if _Dbg_copies ')' $BASH_SUBSHELL { setvar _Dbg_greater = ""${result}${_Dbg_greater}"" setvar _Dbg_less = ""${_Dbg_less}${result//)/(}"" } # Loop over debugger commands. But before reading a debugger # command, we need to make sure IFS is set to spaces to ensure our # two variables (command name and rest of the arguments) are set # correctly. Saving the IFS and setting it to the "normal" value # of space should be done in the DEBUG signal handler entry. # Also, we need to make sure the prompt output is # redirected to the debugger terminal. Both of these things may # have been changed by the debugged program for its own # purposes. Furthermore, were we *not* to redirect our stderr # below, we may mess up what the debugged program expects to see # in in stderr by adding our debugger prompt. # if no tty, no prompt setvar _Dbg_prompt_output = ${_Dbg_tty:-/dev/null} eval "local _Dbg_prompt=$_Dbg_prompt_str" _Dbg_preloop typeset _Dbg_cmd typeset args typeset rc while : { set -o history setvar _Dbg_input_desc = ${_Dbg_fd[_Dbg_fd_last]} if [[ $_Dbg_tty == '&1' ]] { echo -n $_Dbg_prompt if ! read _Dbg_cmd args <&$_Dbg_input_desc 2>&1 { break } } else { if ((_Dbg_set_read_completion)) { setvar _Dbg_read_fn = ''readc'' } else { setvar _Dbg_read_fn = ''read'' } if ! $_Dbg_read_fn $_Dbg_edit -p $_Dbg_prompt _Dbg_cmd args \ <&$_Dbg_input_desc 2>>$_Dbg_prompt_output { set +o history break } } # FIXME: until I figure out to fix builtin readc, this happens # on command completion: if [[ $_Dbg_cmd =~ ' ' && -z $args ]] { typeset -a ary; setvar IFS = '' ',' ary = ''( $_Dbg_cmd ) setvar _Dbg_cmd = ${ary[0]} unset ary[0] setvar args = "${ary[@]}" } set +o history if (( _Dbg_brkpt_commands_defining )) { case (_Dbg_cmd) { silent { setvar _Dbg_brkpt_commands_silent[$_Dbg_brkpt_commands_current]=1 continue } end { setvar _Dbg_brkpt_commands_defining = '0' #### ??? TESTING ## local -i cur=$_Dbg_brkpt_commands_current ## local -i start=${_Dbg_brkpt_commands_start[$cur]} ## local -i end=${_Dbg_brkpt_commands_end[$cur]} ## local -i i ## echo "++ brkpt: $cur, start: $start, end: $end " ## for (( i=start; (( i < end )) ; i++ )) ; do ## echo ${_Dbg_brkpt_commands[$i]} ## done eval "_Dbg_prompt=$_Dbg_prompt_str" continue } * { setvar _Dbg_brkpt_commands[${#_Dbg_brkpt_commands[@]}]="$_Dbg_cmd $args" (( _Dbg_brkpt_commands_end[$_Dbg_brkpt_commands_current]++ )) continue } } setvar rc = ""$? } else { _Dbg_onecmd $_Dbg_cmd $args _Dbg_postcmd } ((_Dbg_continue_rc >= 0)) && return $_Dbg_continue_rc if (( _Dbg_brkpt_commands_defining )) { setvar _Dbg_prompt = ''>'' } else { eval "_Dbg_prompt=$_Dbg_prompt_str" } } # while read $_Dbg_edit -p ... unset _Dbg_fd[_Dbg_fd_last--] } # Loop over all open pending file descriptors # EOF hit. Same as quit without arguments _Dbg_msg '' # Cause since EOF may not have put in. _Dbg_do_quit } # Run a debugger command "annotating" the output proc _Dbg_annotation { typeset label="$1" shift _Dbg_do_print "$label" $[join(ARGV)] _Dbg_do_print '' } # Run a single command # Parameters: _Dbg_cmd and args # proc _Dbg_onecmd { typeset full_cmd=$[join(ARGV)] typeset _Dbg_orig_cmd="$1" typeset expanded_alias; _Dbg_alias_expand $_Dbg_orig_cmd typeset _Dbg_cmd="$expanded_alias" shift typeset _Dbg_args="$[join(ARGV)]" # Set default next, step or skip command if [[ -z $_Dbg_cmd ]] { setvar _Dbg_cmd = "$_Dbg_last_next_step_cmd" setvar _Dbg_args = "$_Dbg_last_next_step_args" setvar full_cmd = ""$_Dbg_cmd $_Dbg_args"" } # If "set trace-commands" is "on", echo the the command if [[ $_Dbg_set_trace_commands == 'on' ]] { _Dbg_msg "+$full_cmd" } local dq_cmd=$(_Dbg_esc_dq "$_Dbg_cmd") local dq_args=$(_Dbg_esc_dq "$_Dbg_args") # _Dbg_write_journal_eval doesn't work here. Don't really understand # how to get it to work. So we do this in two steps. _Dbg_write_journal \ "_Dbg_history[${#_Dbg_history[@]}]=\"$dq_cmd $dq_args\"" setvar _Dbg_history[${#_Dbg_history[@]}]="$_Dbg_cmd $_Dbg_args" setvar _Dbg_hi = ${#_Dbg_history[@]} history -s -- $full_cmd typeset -i _Dbg_redo=1 { setvar _Dbg_redo = '0' [[ -z $_Dbg_cmd ]] && setvar _Dbg_cmd = "$_Dbg_last_cmd" if [[ -n $_Dbg_cmd ]] { typeset -i found=0 typeset found_cmd if [[ -n ${_Dbg_debugger_commands[$_Dbg_cmd]} ]] { setvar found = '1' setvar found_cmd = "$_Dbg_cmd" } else { # Look for a unique abbreviation typeset -i count=0 typeset list; setvar list = "${!_Dbg_debugger_commands[@]}" for try in $list { if [[ $try =~ ^$_Dbg_cmd ]] { setvar found_cmd = "$try" ((count++)) } } ((found=(count==1))) } if ((found)) { ${_Dbg_debugger_commands[$found_cmd]} $_Dbg_args setvar IFS = "$_Dbg_space_IFS"; eval "_Dbg_prompt=$_Dbg_prompt_str" ((_Dbg_continue_rc >= 0)) && return $_Dbg_continue_rc continue } } case (_Dbg_cmd) { # Comment line [#]* { _Dbg_history_remove_item setvar _Dbg_last_cmd = ''#'' } # list current line . { _Dbg_list $_Dbg_frame_last_filename $_Dbg_frame_last_lineno 1 setvar _Dbg_last_cmd = ''list'' } # Search forwards for pattern /* { _Dbg_do_search $_Dbg_cmd setvar _Dbg_last_cmd = ''search'' } # Search backwards for pattern [?]* { _Dbg_do_reverse $_Dbg_cmd setvar _Dbg_last_cmd = ""reverse"" } # Change Directory cd { # Allow for tilde expansion. We also allow expansion of # variables like $HOME which gdb doesn't allow. That's life. local cd_command="cd $_Dbg_args" eval $cd_command _Dbg_do_pwd setvar _Dbg_last_cmd = ''cd'' } # complete comp | compl | comple | complet | complete { _Dbg_do_complete $_Dbg_args setvar _Dbg_last_cmd = ''complete'' } # Set up a script for debugging into. debug { _Dbg_do_debug $_Dbg_args # Skip over the execute statement which presumably we ran above. _Dbg_do_next_skip 'skip' 1 setvar IFS = "$_Dbg_old_IFS"; return 1 setvar _Dbg_last_cmd = ''debug'' } # Delete all breakpoints. D | deletea | deleteal | deleteall { _Dbg_clear_all_brkpt setvar _Dbg_last_cmd = ''deleteall'' } # return from function/source without finishing executions return { } # run shell command. Has to come before ! below. shell | '!!' { eval $_Dbg_args } # Send signal to process si | sig | sign | signa | signal { _Dbg_do_signal $_Dbg_args setvar _Dbg_last_cmd = ''signal'' } # single-step 'step+' | 'step-' { _Dbg_do_step $_Dbg_cmd $_Dbg_args return 0 } # # toggle execution trace # to | tog | togg | toggl | toggle ) # _Dbg_do_trace # ;; # List all breakpoints and actions. L { _Dbg_do_list_brkpt _Dbg_list_watch _Dbg_list_action } # Remove all actions A { _Dbg_do_clear_all_actions $_Dbg_args } # List debugger command history H { _Dbg_history_remove_item _Dbg_do_history_list $_Dbg_args } # S List subroutine names S { _Dbg_do_list_functions $_Dbg_args } # Dump variables V { _Dbg_do_info_variables $_Dbg_args } # Has to come after !! of "shell" listed above # Run an item from the command history \!* | history { _Dbg_do_history $_Dbg_args } '' { # Redo last_cmd if [[ -n $_Dbg_last_cmd ]] { setvar _Dbg_cmd = "$_Dbg_last_cmd" setvar _Dbg_redo = '1' } } * { if (( _Dbg_set_autoeval )) { _Dbg_do_eval $_Dbg_cmd $_Dbg_args } else { _Dbg_undefined_cmd $_Dbg_cmd _Dbg_history_remove_item # local -a last_history=(`history 1`) # history -d ${last_history[0]} } } } } # while (( $_Dbg_redo )) setvar IFS = "$_Dbg_space_IFS"; eval "_Dbg_prompt=$_Dbg_prompt_str" } proc _Dbg_preloop { if ((_Dbg_set_annotate)) { _Dbg_annotation 'breakpoints' _Dbg_do_info breakpoints # _Dbg_annotation 'locals' _Dbg_do_backtrace 3 _Dbg_annotation 'stack' _Dbg_do_backtrace 3 } } proc _Dbg_postcmd { if ((_Dbg_set_annotate)) { case (_Dbg_last_cmd) { break | tbreak | disable | enable | condition | clear | delete { _Dbg_annotation 'breakpoints' _Dbg_do_info breakpoints } up | down | frame { _Dbg_annotation 'stack' _Dbg_do_backtrace 3 } * { } } } }