# -*- 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 compat array-assign _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; setglobal _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; setglobal _Dbg_cmdfile = ''('') # A list of debugger command input-file descriptors. typeset -a _Dbg_fd; setglobal _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. setglobal _Dbg_inside_skip = '0' setglobal _Dbg_step_ignore = '-1' # Nuke any prior step ignore counts setglobal _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]) } # Loop over all pending open input file descriptors while sh-expr ' _Dbg_fd_last > 0' { # 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 { setglobal _Dbg_greater = $result setglobal _Dbg_less = $(result//>/<) } if _Dbg_copies ')' $BASH_SUBSHELL { setglobal _Dbg_greater = ""$(result)$(_Dbg_greater)"" setglobal _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 setglobal _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 setglobal _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 sh-expr '_Dbg_set_read_completion' { setglobal _Dbg_read_fn = ''readc'' } else { setglobal _Dbg_read_fn = ''read'' } if ! $_Dbg_read_fn $_Dbg_edit -p $_Dbg_prompt _Dbg_cmd args \ <&$_Dbg_input_desc !2 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; setglobal IFS = '' ',' ary = ''( $_Dbg_cmd ) setglobal _Dbg_cmd = $(ary[0]) unset ary[0] setglobal args = $(ary[@]) } set +o history if sh-expr ' _Dbg_brkpt_commands_defining ' { match $_Dbg_cmd { with silent compat array-assign _Dbg_brkpt_commands_silent '$_Dbg_brkpt_commands_current' '1' continue with end setglobal _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 with * compat array-assign _Dbg_brkpt_commands '${#_Dbg_brkpt_commands[@]}' ""$_Dbg_cmd $args"" sh-expr ' _Dbg_brkpt_commands_end[$_Dbg_brkpt_commands_current]++ ' continue } setglobal rc = $Status } else { _Dbg_onecmd $_Dbg_cmd $args _Dbg_postcmd } sh-expr '_Dbg_continue_rc >= 0' && return $_Dbg_continue_rc if sh-expr ' _Dbg_brkpt_commands_defining ' { setglobal _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" $ifsjoin(Argv) _Dbg_do_print '' } # Run a single command # Parameters: _Dbg_cmd and args # proc _Dbg_onecmd { typeset full_cmd=$ifsjoin(Argv) typeset _Dbg_orig_cmd="$1" typeset expanded_alias; _Dbg_alias_expand $_Dbg_orig_cmd typeset _Dbg_cmd="$expanded_alias" shift typeset _Dbg_args="$ifsjoin(Argv)" # Set default next, step or skip command if [[ -z $_Dbg_cmd ]] { setglobal _Dbg_cmd = $_Dbg_last_next_step_cmd setglobal _Dbg_args = $_Dbg_last_next_step_args setglobal 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\"" compat array-assign _Dbg_history '${#_Dbg_history[@]}' ""$_Dbg_cmd $_Dbg_args"" setglobal _Dbg_hi = $(#_Dbg_history[@]) history -s -- $full_cmd typeset -i _Dbg_redo=1 while sh-expr ' _Dbg_redo ' { setglobal _Dbg_redo = '0' [[ -z $_Dbg_cmd ]] && setglobal _Dbg_cmd = $_Dbg_last_cmd if [[ -n $_Dbg_cmd ]] { typeset -i found=0 typeset found_cmd if [[ -n ${_Dbg_debugger_commands[$_Dbg_cmd]} ]] { setglobal found = '1' setglobal found_cmd = $_Dbg_cmd } else { # Look for a unique abbreviation typeset -i count=0 typeset list; setglobal list = $(!_Dbg_debugger_commands[@]) for try in [$list] { if [[ $try =~ ^$_Dbg_cmd ]] { setglobal found_cmd = $try sh-expr 'count++' } } sh-expr 'found=(count==1)' } if sh-expr 'found' { $(_Dbg_debugger_commands[$found_cmd]) $_Dbg_args setglobal IFS = $_Dbg_space_IFS; eval "_Dbg_prompt=$_Dbg_prompt_str" sh-expr '_Dbg_continue_rc >= 0' && return $_Dbg_continue_rc continue } } match $_Dbg_cmd { # Comment line with [#]* _Dbg_history_remove_item setglobal _Dbg_last_cmd = ''#'' # list current line with . _Dbg_list $_Dbg_frame_last_filename $_Dbg_frame_last_lineno 1 setglobal _Dbg_last_cmd = ''list'' # Search forwards for pattern with /* _Dbg_do_search $_Dbg_cmd setglobal _Dbg_last_cmd = ''search'' # Search backwards for pattern with [?]* _Dbg_do_reverse $_Dbg_cmd setglobal _Dbg_last_cmd = '"reverse'" # Change Directory with 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 setglobal _Dbg_last_cmd = ''cd'' # complete with comp | compl | comple | complet | complete _Dbg_do_complete $_Dbg_args setglobal _Dbg_last_cmd = ''complete'' # Set up a script for debugging into. with debug _Dbg_do_debug $_Dbg_args # Skip over the execute statement which presumably we ran above. _Dbg_do_next_skip 'skip' 1 setglobal IFS = $_Dbg_old_IFS; return 1 setglobal _Dbg_last_cmd = ''debug'' # Delete all breakpoints. with D | deletea | deleteal | deleteall _Dbg_clear_all_brkpt setglobal _Dbg_last_cmd = ''deleteall'' # return from function/source without finishing executions with return # run shell command. Has to come before ! below. with shell | '!!' eval $_Dbg_args # Send signal to process with si | sig | sign | signa | signal _Dbg_do_signal $_Dbg_args setglobal _Dbg_last_cmd = ''signal'' # single-step with '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. with L _Dbg_do_list_brkpt _Dbg_list_watch _Dbg_list_action # Remove all actions with A _Dbg_do_clear_all_actions $_Dbg_args # List debugger command history with H _Dbg_history_remove_item _Dbg_do_history_list $_Dbg_args # S List subroutine names with S _Dbg_do_list_functions $_Dbg_args # Dump variables with V _Dbg_do_info_variables $_Dbg_args # Has to come after !! of "shell" listed above # Run an item from the command history with \!* | history _Dbg_do_history $_Dbg_args with '' # Redo last_cmd if [[ -n $_Dbg_last_cmd ]] { setglobal _Dbg_cmd = $_Dbg_last_cmd setglobal _Dbg_redo = '1' } with * if sh-expr ' _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 )) setglobal IFS = $_Dbg_space_IFS; eval "_Dbg_prompt=$_Dbg_prompt_str" } proc _Dbg_preloop { if sh-expr '_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 sh-expr '_Dbg_set_annotate' { match $_Dbg_last_cmd { with break | tbreak | disable | enable | condition | clear | delete _Dbg_annotation 'breakpoints' _Dbg_do_info breakpoints with up | down | frame _Dbg_annotation 'stack' _Dbg_do_backtrace 3 with * } } }