diff mbox

RFC: block of commands

Message ID 1448131372.2153.17.camel@skynet.be
State New
Headers show

Commit Message

Philippe Waroquiers Nov. 21, 2015, 6:42 p.m. UTC
The included patch implements the concept of a 'block of commands'.
A block of commands is one or more commands, separated by ;.
A block of command starts with {.
Block of commands can be included (nested) in block of commands.
If nested blocks are used, each block must be terminated by }.
{, } and ;  can be escaped with \ if needed.

A block of command can optionally start with a control flag that
controls the behaviour when a command of the block fails.
The /c flag indicates to output the error and continue the block.
The /s flag indicates to continue the block, without outputting the error.

The block of commands can be used in the
   'thread apply' command,
to execute more than one command for each thread.

The backtrace command has been modified to (optionally) accept a
trailing block of commands.

Command qualifiers /s have been added to backtrace and thread apply.
/s qualifier indicates to only output a frame (or thread) info if
the given block of commands (or command for thread apply) has produced
some output.

A block of command can be used in a user defined command, to avoid
having a command failing to stop the execution of the user defined
command.

And finally, such a block can be used interactively, to group some
commands and e.g. retrieve and execute a group from the history.

Some example of usage:
   bt {/c p i
     => show all frames, show var i (if it exists), otherwise show an error)

   bt {/s p i
     => same, but do not show an error

   bt /s {/s p i
     => only show the frames where i can be succesfully printed


   thread apply all bt {/s p i
     => show a backtrace for all threads, print i in each frame (if it exists)

   thread apply all /s bt /s {/s p i
     => show only the threads and thread frames with a var i.

   thread apply all { set $prevsp = $sp; bt \{ p $ebp - $prevsp\; set $prevsp = $sp \} }
     => print the size of each frame

   define robustshow
   {/c print i }
   {/c print j }
   end
      => define a command that will print i and/or j.


The patch has been (somewhat) manually tested.
Not yet done: tests, user manual, NEWS, ChangeLog.

Comments welcome on the idea and/or the patch.

Thanks

Philippe

Comments

Philippe Waroquiers Dec. 3, 2015, 10:30 p.m. UTC | #1
Any feedback about the idea below (or the patch) ?
Thanks
Philippe

On Sat, 2015-11-21 at 19:42 +0100, Philippe Waroquiers wrote:
> The included patch implements the concept of a 'block of commands'.
> A block of commands is one or more commands, separated by ;.
> A block of command starts with {.
> Block of commands can be included (nested) in block of commands.
> If nested blocks are used, each block must be terminated by }.
> {, } and ;  can be escaped with \ if needed.
> 
> A block of command can optionally start with a control flag that
> controls the behaviour when a command of the block fails.
> The /c flag indicates to output the error and continue the block.
> The /s flag indicates to continue the block, without outputting the error.
> 
> The block of commands can be used in the
>    'thread apply' command,
> to execute more than one command for each thread.
> 
> The backtrace command has been modified to (optionally) accept a
> trailing block of commands.
> 
> Command qualifiers /s have been added to backtrace and thread apply.
> /s qualifier indicates to only output a frame (or thread) info if
> the given block of commands (or command for thread apply) has produced
> some output.
> 
> A block of command can be used in a user defined command, to avoid
> having a command failing to stop the execution of the user defined
> command.
> 
> And finally, such a block can be used interactively, to group some
> commands and e.g. retrieve and execute a group from the history.
> 
> Some example of usage:
>    bt {/c p i
>      => show all frames, show var i (if it exists), otherwise show an error)
> 
>    bt {/s p i
>      => same, but do not show an error
> 
>    bt /s {/s p i
>      => only show the frames where i can be succesfully printed
> 
> 
>    thread apply all bt {/s p i
>      => show a backtrace for all threads, print i in each frame (if it exists)
> 
>    thread apply all /s bt /s {/s p i
>      => show only the threads and thread frames with a var i.
> 
>    thread apply all { set $prevsp = $sp; bt \{ p $ebp - $prevsp\; set $prevsp = $sp \} }
>      => print the size of each frame
> 
>    define robustshow
>    {/c print i }
>    {/c print j }
>    end
>       => define a command that will print i and/or j.
> 
> 
> The patch has been (somewhat) manually tested.
> Not yet done: tests, user manual, NEWS, ChangeLog.
> 
> Comments welcome on the idea and/or the patch.
> 
> Thanks
> 
> Philippe
> 
> diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c
> index cab2336..615e543 100644
> --- a/gdb/cli/cli-decode.c
> +++ b/gdb/cli/cli-decode.c
> @@ -1255,6 +1255,11 @@ find_command_name_length (const char *text)
>    if (*p == '!')
>      return 1;
>  
> +  /* Similarly, recognize '{' as a single character command so that
> +     a command block works as expected.  */
> +  if (*p == '{')
> +    return 1;
> +
>    while (isalnum (*p) || *p == '-' || *p == '_'
>  	 /* Characters used by TUI specific commands.  */
>  	 || *p == '+' || *p == '<' || *p == '>' || *p == '$')
> diff --git a/gdb/stack.c b/gdb/stack.c
> index b825bdf..d8d9791 100644
> --- a/gdb/stack.c
> +++ b/gdb/stack.c
> @@ -1712,10 +1712,14 @@ frame_info (char *addr_exp, int from_tty)
>  }
>  
>  /* Print briefly all stack frames or just the innermost COUNT_EXP
> -   frames.  */
> +   frames.  If cmd != NULL, executes cmd in the context of each
> +   printed stack frame.  If silent, only print stack frames
> +   for which cmd has produced some output.  */
>  
>  static void
> -backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
> +backtrace_command_1 (char *count_exp,
> +		     char *cmd, int silent,
> +		     int show_locals, int no_filters,
>  		     int from_tty)
>  {
>    struct frame_info *fi;
> @@ -1820,8 +1824,42 @@ backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
>      {
>        for (i = 0, fi = trailing; fi && count--; i++, fi = get_prev_frame (fi))
>  	{
> +	  char *cmd_result = NULL;
> +
>  	  QUIT;
>  
> +	  if (cmd != NULL)
> +	    {
> +	      char *saved_cmd;
> +	      struct frame_id frame_id = get_frame_id (fi);
> +
> +	      /* Save a copy of the command in case it is clobbered by
> +		 execute_command.  */
> +	      saved_cmd = xstrdup (cmd);
> +	      make_cleanup (xfree, saved_cmd);
> +
> +	      select_frame (fi);
> +	      cmd_result = execute_command_to_string (cmd, from_tty);
> +
> +	      /* Restore exact command used previously.  */
> +	      strcpy (cmd, saved_cmd);
> +
> +	      /* execute_command (might) invalidates FI.  */
> +	      fi = frame_find_by_id (frame_id);
> +	      if (fi == NULL)
> +		{
> +		  trailing = NULL;
> +		  warning (_("Unable to restore previously selected frame."));
> +		  break;
> +		}
> +	    }
> +
> +	  if (silent && (cmd_result == NULL || *cmd_result == 0))
> +	    {
> +	      xfree (cmd_result);
> +	      continue;
> +	    }
> +
>  	  /* Don't use print_stack_frame; if an error() occurs it probably
>  	     means further attempts to backtrace would fail (on the other
>  	     hand, perhaps the code does or could be fixed to make sure
> @@ -1843,6 +1881,13 @@ backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
>  		  break;
>  		}
>  	    }
> +	  if (cmd_result != NULL)
> +	    {
> +	      if (*cmd_result != 0)
> +		printf_filtered ("%s", cmd_result);
> +	      xfree (cmd_result);
> +	    }
> +
>  
>  	  /* Save the last frame to check for error conditions.  */
>  	  trailing = fi;
> @@ -1871,6 +1916,9 @@ backtrace_command (char *arg, int from_tty)
>  {
>    struct cleanup *old_chain = make_cleanup (null_cleanup, NULL);
>    int fulltrace_arg = -1, arglen = 0, argc = 0, no_filters  = -1;
> +  int cmd_arg = -1;
> +  char *cmd = NULL;
> +  int silent_arg = -1;
>    int user_arg = 0;
>  
>    if (arg)
> @@ -1890,29 +1938,39 @@ backtrace_command (char *arg, int from_tty)
>  
>  	  if (no_filters < 0 && subset_compare (argv[i], "no-filters"))
>  	    no_filters = argc;
> +	  else if (fulltrace_arg < 0 && subset_compare (argv[i], "full"))
> +	    fulltrace_arg = argc;
> +	  else if (silent_arg < 0 && subset_compare (argv[i], "/s"))
> +	    silent_arg = argc;
> +	  else if (cmd_arg < 0 && subset_compare ("{", argv[i]))
> +	     {
> +		char *block = strchr(arg, '{');
> +
> +		cmd_arg = argc;
> +		cmd = xmalloc (strlen (block) + 1);
> +		strcpy (cmd, block);
> +		make_cleanup (xfree, cmd);
> +		break;
> +	     }
>  	  else
>  	    {
> -	      if (fulltrace_arg < 0 && subset_compare (argv[i], "full"))
> -		fulltrace_arg = argc;
> -	      else
> -		{
> -		  user_arg++;
> -		  arglen += strlen (argv[i]);
> -		}
> +	      user_arg++;
> +	      arglen += strlen (argv[i]);
>  	    }
>  	  argc++;
>  	}
>        arglen += user_arg;
> -      if (fulltrace_arg >= 0 || no_filters >= 0)
> +      if (fulltrace_arg >= 0 || no_filters >= 0
> +	  || cmd_arg >= 0 || silent_arg > 0)
>  	{
>  	  if (arglen > 0)
>  	    {
>  	      arg = (char *) xmalloc (arglen + 1);
>  	      make_cleanup (xfree, arg);
>  	      arg[0] = 0;
> -	      for (i = 0; i < argc; i++)
> +	      for (i = 0; i < argc && i != cmd_arg; i++)
>  		{
> -		  if (i != fulltrace_arg && i != no_filters)
> +		  if (i != fulltrace_arg && i != no_filters && i != silent_arg)
>  		    {
>  		      strcat (arg, argv[i]);
>  		      strcat (arg, " ");
> @@ -1924,7 +1982,8 @@ backtrace_command (char *arg, int from_tty)
>  	}
>      }
>  
> -  backtrace_command_1 (arg, fulltrace_arg >= 0 /* show_locals */,
> +  backtrace_command_1 (arg, cmd, silent_arg >= 0, /* silent */
> +		       fulltrace_arg >= 0 /* show_locals */,
>  		       no_filters >= 0 /* no frame-filters */, from_tty);
>  
>    do_cleanups (old_chain);
> @@ -2600,7 +2659,12 @@ Print backtrace of all stack frames, or innermost COUNT frames.\n\
>  With a negative argument, print outermost -COUNT frames.\nUse of the \
>  'full' qualifier also prints the values of the local variables.\n\
>  Use of the 'no-filters' qualifier prohibits frame filters from executing\n\
> -on this backtrace.\n"));
> +on this backtrace.\n\
> +After the backtrace arguments and qualifiers, you can specify a block of commands.\n\
> +The block of commands will be executed for each each printed stack frame.\n\
> +Example:    backtrace -5 {/s print i; print j}\n\
> +will print the variables i and j (if they exists) in all printed stack frames.\n\
> +   use    help {    for the syntax of a block of command."));
>    add_com_alias ("bt", "backtrace", class_stack, 0);
>  
>    add_com_alias ("where", "backtrace", class_alias, 0);
> diff --git a/gdb/thread.c b/gdb/thread.c
> index 6d410fb..8fdcfb8 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -1596,6 +1596,7 @@ thread_apply_all_command (char *cmd, int from_tty)
>  {
>    struct cleanup *old_chain;
>    char *saved_cmd;
> +  int silent = 0;
>    int tc;
>    struct thread_array_cleanup ta_cleanup;
>  
> @@ -1607,6 +1608,13 @@ thread_apply_all_command (char *cmd, int from_tty)
>        tp_array_compar_ascending = 1;
>      }
>  
> +  if (cmd != NULL
> +      && check_for_argument (&cmd, "/s", strlen ("/s")))
> +    {
> +      cmd = skip_spaces (cmd);
> +      silent = 1;
> +    }
> +
>    if (cmd == NULL || *cmd == '\000')
>      error (_("Please specify a command following the thread ID list"));
>  
> @@ -1652,11 +1660,23 @@ thread_apply_all_command (char *cmd, int from_tty)
>        for (k = 0; k != i; k++)
>          if (thread_alive (tp_array[k]))
>            {
> +	    char *cmd_result = NULL;
> +
>              switch_to_thread (tp_array[k]->ptid);
> -            printf_filtered (_("\nThread %d (%s):\n"), 
> -			     tp_array[k]->num,
> -			     target_pid_to_str (inferior_ptid));
> -            execute_command (cmd, from_tty);
> +	    if (silent)
> +	      cmd_result = execute_command_to_string (cmd, from_tty);
> +	    if (!silent || (cmd_result != NULL && *cmd_result != 0))
> +	      printf_filtered (_("\nThread %d (%s):\n"),
> +			       tp_array[k]->num,
> +			       target_pid_to_str (inferior_ptid));
> +	    if (cmd_result)
> +	      {
> +		if (*cmd_result != 0)
> +		  printf_filtered ("%s", cmd_result);
> +		xfree (cmd_result);
> +	      }
> +	    else
> +	      execute_command (cmd, from_tty);
>  
>              /* Restore exact command used previously.  */
>              strcpy (cmd, saved_cmd);
> @@ -1677,7 +1697,9 @@ thread_apply_command (char *tidlist, int from_tty)
>    if (tidlist == NULL || *tidlist == '\000')
>      error (_("Please specify a thread ID list"));
>  
> -  for (cmd = tidlist; *cmd != '\000' && !isalpha (*cmd); cmd++);
> +  for (cmd = tidlist;
> +       *cmd != '\000' && !isalpha (*cmd) && *cmd != '{';
> +       cmd++);
>  
>    if (*cmd == '\000')
>      error (_("Please specify a command following the thread ID list"));
> diff --git a/gdb/top.c b/gdb/top.c
> index d1e2271..b553028 100644
> --- a/gdb/top.c
> +++ b/gdb/top.c
> @@ -534,6 +534,151 @@ execute_command_to_string (char *p, int from_tty)
>    return retval;
>  }
>  
> +static void
> +execute_one_block_command (char *cmd, int from_tty,
> +			   int continue_on_failure, int silent_failure)
> +{
> +  if (continue_on_failure)
> +    {
> +      TRY
> +	{
> +	  execute_command (cmd, from_tty);
> +	}
> +      CATCH (e, RETURN_MASK_ERROR)
> +	{
> +	  if (!silent_failure)
> +	    exception_print (gdb_stderr, e);
> +	}
> +      END_CATCH
> +	}
> +  else
> +    execute_command (cmd, from_tty);
> +}
> +
> +/* Parses in cmds optional /CONTROL, then executes each command part
> +   of the block. Returns the position in cmds after the block end.  */
> +static void
> +commands_block_command (char *cmds, int from_tty)
> +{
> +  int continue_on_failure = 0;
> +  int silent_failure = 0;
> +  int level = 0;
> +  char *cmd_end = NULL;
> +
> +#if 0
> +#define XX(msg) printf("%s cmds%s <%s> %p cmd_end <%s> %p\n", \
> +		       msg,					\
> +		       silent_failure ? "/s" :			\
> +		       continue_on_failure ? "/c" : "",		\
> +		       cmds, cmds, cmd_end, cmd_end)
> +#else
> +#define XX(msg)
> +#endif
> +
> +  cmds = skip_spaces (cmds);
> +
> +  if (cmds == NULL || *cmds == 0)
> +    error (_("Please specify one or more commands separated by ;,\n"
> +	     "optionally followed by a block end }"));
> +
> +  /* Parses the optional /CONTROL.  */
> +  if (*cmds == '/')
> +    {
> +      cmds++;
> +      while (*cmds) {
> +	if (*cmds == ' ')
> +	  break;
> +	else if (*cmds == 's')
> +	  continue_on_failure = silent_failure = 1;
> +	else if (*cmds == 'c')
> +	  continue_on_failure = 1;
> +	else
> +	  error (_("Invalid commands block control flag %c"), *cmds);
> +	cmds++;
> +      }
> +    }
> +
> +  cmds = skip_spaces (cmds);
> +  cmd_end = cmds;
> +  level = 0;
> +  XX("enter");
> +  while (*cmd_end)
> +    {
> +      if (*cmd_end == '\\'
> +	  && (*(cmd_end+1) == '{'
> +	      || *(cmd_end+1) == '}'
> +	      || *(cmd_end+1) == ';'))
> +	{
> +	  char *p, *q;
> +
> +	  p = cmd_end;
> +	  q = cmd_end+1;
> +	  while (*q)
> +	    *p++ = *q++;
> +	  cmd_end++;
> +	}
> +      else if (*cmd_end == '{')
> +	{
> +	  /* Found the begin of a nested block.  */
> +
> +	  /* Execute last command of including block, if any.  */
> +	  if (cmds <= cmd_end)
> +	    {
> +	      *cmd_end = 0;
> +	      XX("prev block lastcmd");
> +	      execute_one_block_command (cmds, from_tty,
> +					 continue_on_failure, silent_failure);
> +	      *cmd_end = '{';
> +	    }
> +
> +	  /* Search for the nested block end: either a } or end of cmds.  */
> +	  cmds = cmd_end;
> +	  while (*cmd_end) {
> +	    if (*cmd_end == '{')
> +	      level++;
> +	    else if (*cmd_end == '}')
> +	      level--;
> +	    if (level == 0)
> +	      break;
> +	    cmd_end++;
> +	  }
> +
> +	  /* recursively executes the block.  */
> +	  *cmd_end = 0;
> +	  XX("block");
> +	  execute_one_block_command (cmds, from_tty,
> +				     continue_on_failure, silent_failure);
> +	  if (level == 0)
> +	    cmd_end++;
> +	  cmds = cmd_end;
> +	}
> +      else if ((*cmd_end == ';' || *cmd_end == '}') && level == 0)
> +	{
> +	  /*  When encountering a command terminator or a block end,
> +	      executes this command.  Note : the block end comparison
> +	      is needed to execute the last command of a block, if
> +	      this command is not terminated by ;.  */
> +	  *cmd_end = 0;
> +	  XX("cmd");
> +	  execute_one_block_command (cmds, from_tty,
> +				     continue_on_failure, silent_failure);
> +	  cmd_end++;
> +	  cmds = cmd_end;
> +	}
> +      else
> +	cmd_end++;
> +    }
> +
> +  /* execute last command of this block, if any */
> +  if (cmds <= cmd_end && *cmds)
> +    {
> +      XX("lastcmd");
> +      execute_one_block_command (cmds, from_tty,
> +				 continue_on_failure, silent_failure);
> +    }
> +#undef XX
> +}
> +
>  /* Read commands from `instream' and execute them
>     until end of file or error reading instream.  */
>  
> @@ -1942,6 +2087,17 @@ Don't repeat this command.\nPrimarily \
>  used inside of user-defined commands that should not be repeated when\n\
>  hitting return."));
>  
> +  add_com ("{", class_support,
> +	   commands_block_command, _("\
> +Executes a block of commands : {/CONTROL CMD1; CMD2; ... }.\n\
> +Commands are separated by the character ';'.\n\
> +Nested blocks can be terminated by the character '}'.\n\
> +By default, the execution of a block stops if a CMD fails.\n\
> +/CONTROL allows to control the execution in case of a CMD failure:\n\
> +  /c indicates to report the error and continue the block execution.\n\
> +  /s indicates to silently continue the block execution.\n\
> +You can use '\\' to escape '{', '}' and ';'."));
> +
>    add_setshow_boolean_cmd ("editing", class_support,
>  			   &async_command_editing_p, _("\
>  Set editing of command lines as they are typed."), _("\
> 
> 
> 
> 
>
Philippe Waroquiers Dec. 21, 2015, 1:37 p.m. UTC | #2
Ping^2

If not much time, maybe just give feedback about this question:
Should } (termination of the block) be optional or mandatory ?
In the current patch, it is optional (which means that a block of
commands must be given on one line, as the end of line automatically
finishes all unterminated blocks).

If } is mandatory, then I think the parser can be modified to accept
multi-line block of commands.

What is preferrable ?

Thanks

Philippe



On Thu, 2015-12-03 at 23:30 +0100, Philippe Waroquiers wrote:
> Any feedback about the idea below (or the patch) ?
> Thanks
> Philippe
> 
> On Sat, 2015-11-21 at 19:42 +0100, Philippe Waroquiers wrote:
> > The included patch implements the concept of a 'block of commands'.
> > A block of commands is one or more commands, separated by ;.
> > A block of command starts with {.
> > Block of commands can be included (nested) in block of commands.
> > If nested blocks are used, each block must be terminated by }.
> > {, } and ;  can be escaped with \ if needed.
> > 
> > A block of command can optionally start with a control flag that
> > controls the behaviour when a command of the block fails.
> > The /c flag indicates to output the error and continue the block.
> > The /s flag indicates to continue the block, without outputting the error.
> > 
> > The block of commands can be used in the
> >    'thread apply' command,
> > to execute more than one command for each thread.
> > 
> > The backtrace command has been modified to (optionally) accept a
> > trailing block of commands.
> > 
> > Command qualifiers /s have been added to backtrace and thread apply.
> > /s qualifier indicates to only output a frame (or thread) info if
> > the given block of commands (or command for thread apply) has produced
> > some output.
> > 
> > A block of command can be used in a user defined command, to avoid
> > having a command failing to stop the execution of the user defined
> > command.
> > 
> > And finally, such a block can be used interactively, to group some
> > commands and e.g. retrieve and execute a group from the history.
> > 
> > Some example of usage:
> >    bt {/c p i
> >      => show all frames, show var i (if it exists), otherwise show an error)
> > 
> >    bt {/s p i
> >      => same, but do not show an error
> > 
> >    bt /s {/s p i
> >      => only show the frames where i can be succesfully printed
> > 
> > 
> >    thread apply all bt {/s p i
> >      => show a backtrace for all threads, print i in each frame (if it exists)
> > 
> >    thread apply all /s bt /s {/s p i
> >      => show only the threads and thread frames with a var i.
> > 
> >    thread apply all { set $prevsp = $sp; bt \{ p $ebp - $prevsp\; set $prevsp = $sp \} }
> >      => print the size of each frame
> > 
> >    define robustshow
> >    {/c print i }
> >    {/c print j }
> >    end
> >       => define a command that will print i and/or j.
> > 
> > 
> > The patch has been (somewhat) manually tested.
> > Not yet done: tests, user manual, NEWS, ChangeLog.
> > 
> > Comments welcome on the idea and/or the patch.
> > 
> > Thanks
> > 
> > Philippe
> > 
> > diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c
> > index cab2336..615e543 100644
> > --- a/gdb/cli/cli-decode.c
> > +++ b/gdb/cli/cli-decode.c
> > @@ -1255,6 +1255,11 @@ find_command_name_length (const char *text)
> >    if (*p == '!')
> >      return 1;
> >  
> > +  /* Similarly, recognize '{' as a single character command so that
> > +     a command block works as expected.  */
> > +  if (*p == '{')
> > +    return 1;
> > +
> >    while (isalnum (*p) || *p == '-' || *p == '_'
> >  	 /* Characters used by TUI specific commands.  */
> >  	 || *p == '+' || *p == '<' || *p == '>' || *p == '$')
> > diff --git a/gdb/stack.c b/gdb/stack.c
> > index b825bdf..d8d9791 100644
> > --- a/gdb/stack.c
> > +++ b/gdb/stack.c
> > @@ -1712,10 +1712,14 @@ frame_info (char *addr_exp, int from_tty)
> >  }
> >  
> >  /* Print briefly all stack frames or just the innermost COUNT_EXP
> > -   frames.  */
> > +   frames.  If cmd != NULL, executes cmd in the context of each
> > +   printed stack frame.  If silent, only print stack frames
> > +   for which cmd has produced some output.  */
> >  
> >  static void
> > -backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
> > +backtrace_command_1 (char *count_exp,
> > +		     char *cmd, int silent,
> > +		     int show_locals, int no_filters,
> >  		     int from_tty)
> >  {
> >    struct frame_info *fi;
> > @@ -1820,8 +1824,42 @@ backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
> >      {
> >        for (i = 0, fi = trailing; fi && count--; i++, fi = get_prev_frame (fi))
> >  	{
> > +	  char *cmd_result = NULL;
> > +
> >  	  QUIT;
> >  
> > +	  if (cmd != NULL)
> > +	    {
> > +	      char *saved_cmd;
> > +	      struct frame_id frame_id = get_frame_id (fi);
> > +
> > +	      /* Save a copy of the command in case it is clobbered by
> > +		 execute_command.  */
> > +	      saved_cmd = xstrdup (cmd);
> > +	      make_cleanup (xfree, saved_cmd);
> > +
> > +	      select_frame (fi);
> > +	      cmd_result = execute_command_to_string (cmd, from_tty);
> > +
> > +	      /* Restore exact command used previously.  */
> > +	      strcpy (cmd, saved_cmd);
> > +
> > +	      /* execute_command (might) invalidates FI.  */
> > +	      fi = frame_find_by_id (frame_id);
> > +	      if (fi == NULL)
> > +		{
> > +		  trailing = NULL;
> > +		  warning (_("Unable to restore previously selected frame."));
> > +		  break;
> > +		}
> > +	    }
> > +
> > +	  if (silent && (cmd_result == NULL || *cmd_result == 0))
> > +	    {
> > +	      xfree (cmd_result);
> > +	      continue;
> > +	    }
> > +
> >  	  /* Don't use print_stack_frame; if an error() occurs it probably
> >  	     means further attempts to backtrace would fail (on the other
> >  	     hand, perhaps the code does or could be fixed to make sure
> > @@ -1843,6 +1881,13 @@ backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
> >  		  break;
> >  		}
> >  	    }
> > +	  if (cmd_result != NULL)
> > +	    {
> > +	      if (*cmd_result != 0)
> > +		printf_filtered ("%s", cmd_result);
> > +	      xfree (cmd_result);
> > +	    }
> > +
> >  
> >  	  /* Save the last frame to check for error conditions.  */
> >  	  trailing = fi;
> > @@ -1871,6 +1916,9 @@ backtrace_command (char *arg, int from_tty)
> >  {
> >    struct cleanup *old_chain = make_cleanup (null_cleanup, NULL);
> >    int fulltrace_arg = -1, arglen = 0, argc = 0, no_filters  = -1;
> > +  int cmd_arg = -1;
> > +  char *cmd = NULL;
> > +  int silent_arg = -1;
> >    int user_arg = 0;
> >  
> >    if (arg)
> > @@ -1890,29 +1938,39 @@ backtrace_command (char *arg, int from_tty)
> >  
> >  	  if (no_filters < 0 && subset_compare (argv[i], "no-filters"))
> >  	    no_filters = argc;
> > +	  else if (fulltrace_arg < 0 && subset_compare (argv[i], "full"))
> > +	    fulltrace_arg = argc;
> > +	  else if (silent_arg < 0 && subset_compare (argv[i], "/s"))
> > +	    silent_arg = argc;
> > +	  else if (cmd_arg < 0 && subset_compare ("{", argv[i]))
> > +	     {
> > +		char *block = strchr(arg, '{');
> > +
> > +		cmd_arg = argc;
> > +		cmd = xmalloc (strlen (block) + 1);
> > +		strcpy (cmd, block);
> > +		make_cleanup (xfree, cmd);
> > +		break;
> > +	     }
> >  	  else
> >  	    {
> > -	      if (fulltrace_arg < 0 && subset_compare (argv[i], "full"))
> > -		fulltrace_arg = argc;
> > -	      else
> > -		{
> > -		  user_arg++;
> > -		  arglen += strlen (argv[i]);
> > -		}
> > +	      user_arg++;
> > +	      arglen += strlen (argv[i]);
> >  	    }
> >  	  argc++;
> >  	}
> >        arglen += user_arg;
> > -      if (fulltrace_arg >= 0 || no_filters >= 0)
> > +      if (fulltrace_arg >= 0 || no_filters >= 0
> > +	  || cmd_arg >= 0 || silent_arg > 0)
> >  	{
> >  	  if (arglen > 0)
> >  	    {
> >  	      arg = (char *) xmalloc (arglen + 1);
> >  	      make_cleanup (xfree, arg);
> >  	      arg[0] = 0;
> > -	      for (i = 0; i < argc; i++)
> > +	      for (i = 0; i < argc && i != cmd_arg; i++)
> >  		{
> > -		  if (i != fulltrace_arg && i != no_filters)
> > +		  if (i != fulltrace_arg && i != no_filters && i != silent_arg)
> >  		    {
> >  		      strcat (arg, argv[i]);
> >  		      strcat (arg, " ");
> > @@ -1924,7 +1982,8 @@ backtrace_command (char *arg, int from_tty)
> >  	}
> >      }
> >  
> > -  backtrace_command_1 (arg, fulltrace_arg >= 0 /* show_locals */,
> > +  backtrace_command_1 (arg, cmd, silent_arg >= 0, /* silent */
> > +		       fulltrace_arg >= 0 /* show_locals */,
> >  		       no_filters >= 0 /* no frame-filters */, from_tty);
> >  
> >    do_cleanups (old_chain);
> > @@ -2600,7 +2659,12 @@ Print backtrace of all stack frames, or innermost COUNT frames.\n\
> >  With a negative argument, print outermost -COUNT frames.\nUse of the \
> >  'full' qualifier also prints the values of the local variables.\n\
> >  Use of the 'no-filters' qualifier prohibits frame filters from executing\n\
> > -on this backtrace.\n"));
> > +on this backtrace.\n\
> > +After the backtrace arguments and qualifiers, you can specify a block of commands.\n\
> > +The block of commands will be executed for each each printed stack frame.\n\
> > +Example:    backtrace -5 {/s print i; print j}\n\
> > +will print the variables i and j (if they exists) in all printed stack frames.\n\
> > +   use    help {    for the syntax of a block of command."));
> >    add_com_alias ("bt", "backtrace", class_stack, 0);
> >  
> >    add_com_alias ("where", "backtrace", class_alias, 0);
> > diff --git a/gdb/thread.c b/gdb/thread.c
> > index 6d410fb..8fdcfb8 100644
> > --- a/gdb/thread.c
> > +++ b/gdb/thread.c
> > @@ -1596,6 +1596,7 @@ thread_apply_all_command (char *cmd, int from_tty)
> >  {
> >    struct cleanup *old_chain;
> >    char *saved_cmd;
> > +  int silent = 0;
> >    int tc;
> >    struct thread_array_cleanup ta_cleanup;
> >  
> > @@ -1607,6 +1608,13 @@ thread_apply_all_command (char *cmd, int from_tty)
> >        tp_array_compar_ascending = 1;
> >      }
> >  
> > +  if (cmd != NULL
> > +      && check_for_argument (&cmd, "/s", strlen ("/s")))
> > +    {
> > +      cmd = skip_spaces (cmd);
> > +      silent = 1;
> > +    }
> > +
> >    if (cmd == NULL || *cmd == '\000')
> >      error (_("Please specify a command following the thread ID list"));
> >  
> > @@ -1652,11 +1660,23 @@ thread_apply_all_command (char *cmd, int from_tty)
> >        for (k = 0; k != i; k++)
> >          if (thread_alive (tp_array[k]))
> >            {
> > +	    char *cmd_result = NULL;
> > +
> >              switch_to_thread (tp_array[k]->ptid);
> > -            printf_filtered (_("\nThread %d (%s):\n"), 
> > -			     tp_array[k]->num,
> > -			     target_pid_to_str (inferior_ptid));
> > -            execute_command (cmd, from_tty);
> > +	    if (silent)
> > +	      cmd_result = execute_command_to_string (cmd, from_tty);
> > +	    if (!silent || (cmd_result != NULL && *cmd_result != 0))
> > +	      printf_filtered (_("\nThread %d (%s):\n"),
> > +			       tp_array[k]->num,
> > +			       target_pid_to_str (inferior_ptid));
> > +	    if (cmd_result)
> > +	      {
> > +		if (*cmd_result != 0)
> > +		  printf_filtered ("%s", cmd_result);
> > +		xfree (cmd_result);
> > +	      }
> > +	    else
> > +	      execute_command (cmd, from_tty);
> >  
> >              /* Restore exact command used previously.  */
> >              strcpy (cmd, saved_cmd);
> > @@ -1677,7 +1697,9 @@ thread_apply_command (char *tidlist, int from_tty)
> >    if (tidlist == NULL || *tidlist == '\000')
> >      error (_("Please specify a thread ID list"));
> >  
> > -  for (cmd = tidlist; *cmd != '\000' && !isalpha (*cmd); cmd++);
> > +  for (cmd = tidlist;
> > +       *cmd != '\000' && !isalpha (*cmd) && *cmd != '{';
> > +       cmd++);
> >  
> >    if (*cmd == '\000')
> >      error (_("Please specify a command following the thread ID list"));
> > diff --git a/gdb/top.c b/gdb/top.c
> > index d1e2271..b553028 100644
> > --- a/gdb/top.c
> > +++ b/gdb/top.c
> > @@ -534,6 +534,151 @@ execute_command_to_string (char *p, int from_tty)
> >    return retval;
> >  }
> >  
> > +static void
> > +execute_one_block_command (char *cmd, int from_tty,
> > +			   int continue_on_failure, int silent_failure)
> > +{
> > +  if (continue_on_failure)
> > +    {
> > +      TRY
> > +	{
> > +	  execute_command (cmd, from_tty);
> > +	}
> > +      CATCH (e, RETURN_MASK_ERROR)
> > +	{
> > +	  if (!silent_failure)
> > +	    exception_print (gdb_stderr, e);
> > +	}
> > +      END_CATCH
> > +	}
> > +  else
> > +    execute_command (cmd, from_tty);
> > +}
> > +
> > +/* Parses in cmds optional /CONTROL, then executes each command part
> > +   of the block. Returns the position in cmds after the block end.  */
> > +static void
> > +commands_block_command (char *cmds, int from_tty)
> > +{
> > +  int continue_on_failure = 0;
> > +  int silent_failure = 0;
> > +  int level = 0;
> > +  char *cmd_end = NULL;
> > +
> > +#if 0
> > +#define XX(msg) printf("%s cmds%s <%s> %p cmd_end <%s> %p\n", \
> > +		       msg,					\
> > +		       silent_failure ? "/s" :			\
> > +		       continue_on_failure ? "/c" : "",		\
> > +		       cmds, cmds, cmd_end, cmd_end)
> > +#else
> > +#define XX(msg)
> > +#endif
> > +
> > +  cmds = skip_spaces (cmds);
> > +
> > +  if (cmds == NULL || *cmds == 0)
> > +    error (_("Please specify one or more commands separated by ;,\n"
> > +	     "optionally followed by a block end }"));
> > +
> > +  /* Parses the optional /CONTROL.  */
> > +  if (*cmds == '/')
> > +    {
> > +      cmds++;
> > +      while (*cmds) {
> > +	if (*cmds == ' ')
> > +	  break;
> > +	else if (*cmds == 's')
> > +	  continue_on_failure = silent_failure = 1;
> > +	else if (*cmds == 'c')
> > +	  continue_on_failure = 1;
> > +	else
> > +	  error (_("Invalid commands block control flag %c"), *cmds);
> > +	cmds++;
> > +      }
> > +    }
> > +
> > +  cmds = skip_spaces (cmds);
> > +  cmd_end = cmds;
> > +  level = 0;
> > +  XX("enter");
> > +  while (*cmd_end)
> > +    {
> > +      if (*cmd_end == '\\'
> > +	  && (*(cmd_end+1) == '{'
> > +	      || *(cmd_end+1) == '}'
> > +	      || *(cmd_end+1) == ';'))
> > +	{
> > +	  char *p, *q;
> > +
> > +	  p = cmd_end;
> > +	  q = cmd_end+1;
> > +	  while (*q)
> > +	    *p++ = *q++;
> > +	  cmd_end++;
> > +	}
> > +      else if (*cmd_end == '{')
> > +	{
> > +	  /* Found the begin of a nested block.  */
> > +
> > +	  /* Execute last command of including block, if any.  */
> > +	  if (cmds <= cmd_end)
> > +	    {
> > +	      *cmd_end = 0;
> > +	      XX("prev block lastcmd");
> > +	      execute_one_block_command (cmds, from_tty,
> > +					 continue_on_failure, silent_failure);
> > +	      *cmd_end = '{';
> > +	    }
> > +
> > +	  /* Search for the nested block end: either a } or end of cmds.  */
> > +	  cmds = cmd_end;
> > +	  while (*cmd_end) {
> > +	    if (*cmd_end == '{')
> > +	      level++;
> > +	    else if (*cmd_end == '}')
> > +	      level--;
> > +	    if (level == 0)
> > +	      break;
> > +	    cmd_end++;
> > +	  }
> > +
> > +	  /* recursively executes the block.  */
> > +	  *cmd_end = 0;
> > +	  XX("block");
> > +	  execute_one_block_command (cmds, from_tty,
> > +				     continue_on_failure, silent_failure);
> > +	  if (level == 0)
> > +	    cmd_end++;
> > +	  cmds = cmd_end;
> > +	}
> > +      else if ((*cmd_end == ';' || *cmd_end == '}') && level == 0)
> > +	{
> > +	  /*  When encountering a command terminator or a block end,
> > +	      executes this command.  Note : the block end comparison
> > +	      is needed to execute the last command of a block, if
> > +	      this command is not terminated by ;.  */
> > +	  *cmd_end = 0;
> > +	  XX("cmd");
> > +	  execute_one_block_command (cmds, from_tty,
> > +				     continue_on_failure, silent_failure);
> > +	  cmd_end++;
> > +	  cmds = cmd_end;
> > +	}
> > +      else
> > +	cmd_end++;
> > +    }
> > +
> > +  /* execute last command of this block, if any */
> > +  if (cmds <= cmd_end && *cmds)
> > +    {
> > +      XX("lastcmd");
> > +      execute_one_block_command (cmds, from_tty,
> > +				 continue_on_failure, silent_failure);
> > +    }
> > +#undef XX
> > +}
> > +
> >  /* Read commands from `instream' and execute them
> >     until end of file or error reading instream.  */
> >  
> > @@ -1942,6 +2087,17 @@ Don't repeat this command.\nPrimarily \
> >  used inside of user-defined commands that should not be repeated when\n\
> >  hitting return."));
> >  
> > +  add_com ("{", class_support,
> > +	   commands_block_command, _("\
> > +Executes a block of commands : {/CONTROL CMD1; CMD2; ... }.\n\
> > +Commands are separated by the character ';'.\n\
> > +Nested blocks can be terminated by the character '}'.\n\
> > +By default, the execution of a block stops if a CMD fails.\n\
> > +/CONTROL allows to control the execution in case of a CMD failure:\n\
> > +  /c indicates to report the error and continue the block execution.\n\
> > +  /s indicates to silently continue the block execution.\n\
> > +You can use '\\' to escape '{', '}' and ';'."));
> > +
> >    add_setshow_boolean_cmd ("editing", class_support,
> >  			   &async_command_editing_p, _("\
> >  Set editing of command lines as they are typed."), _("\
> > 
> > 
> > 
> > 
> > 
>
Philippe Waroquiers Jan. 4, 2016, 7:30 p.m. UTC | #3
Ping ^ 3 ?

On Sat, 2015-11-21 at 19:42 +0100, Philippe Waroquiers wrote:
> The included patch implements the concept of a 'block of commands'.
> A block of commands is one or more commands, separated by ;.
> A block of command starts with {.
> Block of commands can be included (nested) in block of commands.
> If nested blocks are used, each block must be terminated by }.
> {, } and ;  can be escaped with \ if needed.
> 
> A block of command can optionally start with a control flag that
> controls the behaviour when a command of the block fails.
> The /c flag indicates to output the error and continue the block.
> The /s flag indicates to continue the block, without outputting the error.
> 
> The block of commands can be used in the
>    'thread apply' command,
> to execute more than one command for each thread.
> 
> The backtrace command has been modified to (optionally) accept a
> trailing block of commands.
> 
> Command qualifiers /s have been added to backtrace and thread apply.
> /s qualifier indicates to only output a frame (or thread) info if
> the given block of commands (or command for thread apply) has produced
> some output.
> 
> A block of command can be used in a user defined command, to avoid
> having a command failing to stop the execution of the user defined
> command.
> 
> And finally, such a block can be used interactively, to group some
> commands and e.g. retrieve and execute a group from the history.
> 
> Some example of usage:
>    bt {/c p i
>      => show all frames, show var i (if it exists), otherwise show an error)
> 
>    bt {/s p i
>      => same, but do not show an error
> 
>    bt /s {/s p i
>      => only show the frames where i can be succesfully printed
> 
> 
>    thread apply all bt {/s p i
>      => show a backtrace for all threads, print i in each frame (if it exists)
> 
>    thread apply all /s bt /s {/s p i
>      => show only the threads and thread frames with a var i.
> 
>    thread apply all { set $prevsp = $sp; bt \{ p $ebp - $prevsp\; set $prevsp = $sp \} }
>      => print the size of each frame
> 
>    define robustshow
>    {/c print i }
>    {/c print j }
>    end
>       => define a command that will print i and/or j.
> 
> 
> The patch has been (somewhat) manually tested.
> Not yet done: tests, user manual, NEWS, ChangeLog.
> 
> Comments welcome on the idea and/or the patch.
> 
> Thanks
> 
> Philippe
> 
> diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c
> index cab2336..615e543 100644
> --- a/gdb/cli/cli-decode.c
> +++ b/gdb/cli/cli-decode.c
> @@ -1255,6 +1255,11 @@ find_command_name_length (const char *text)
>    if (*p == '!')
>      return 1;
>  
> +  /* Similarly, recognize '{' as a single character command so that
> +     a command block works as expected.  */
> +  if (*p == '{')
> +    return 1;
> +
>    while (isalnum (*p) || *p == '-' || *p == '_'
>  	 /* Characters used by TUI specific commands.  */
>  	 || *p == '+' || *p == '<' || *p == '>' || *p == '$')
> diff --git a/gdb/stack.c b/gdb/stack.c
> index b825bdf..d8d9791 100644
> --- a/gdb/stack.c
> +++ b/gdb/stack.c
> @@ -1712,10 +1712,14 @@ frame_info (char *addr_exp, int from_tty)
>  }
>  
>  /* Print briefly all stack frames or just the innermost COUNT_EXP
> -   frames.  */
> +   frames.  If cmd != NULL, executes cmd in the context of each
> +   printed stack frame.  If silent, only print stack frames
> +   for which cmd has produced some output.  */
>  
>  static void
> -backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
> +backtrace_command_1 (char *count_exp,
> +		     char *cmd, int silent,
> +		     int show_locals, int no_filters,
>  		     int from_tty)
>  {
>    struct frame_info *fi;
> @@ -1820,8 +1824,42 @@ backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
>      {
>        for (i = 0, fi = trailing; fi && count--; i++, fi = get_prev_frame (fi))
>  	{
> +	  char *cmd_result = NULL;
> +
>  	  QUIT;
>  
> +	  if (cmd != NULL)
> +	    {
> +	      char *saved_cmd;
> +	      struct frame_id frame_id = get_frame_id (fi);
> +
> +	      /* Save a copy of the command in case it is clobbered by
> +		 execute_command.  */
> +	      saved_cmd = xstrdup (cmd);
> +	      make_cleanup (xfree, saved_cmd);
> +
> +	      select_frame (fi);
> +	      cmd_result = execute_command_to_string (cmd, from_tty);
> +
> +	      /* Restore exact command used previously.  */
> +	      strcpy (cmd, saved_cmd);
> +
> +	      /* execute_command (might) invalidates FI.  */
> +	      fi = frame_find_by_id (frame_id);
> +	      if (fi == NULL)
> +		{
> +		  trailing = NULL;
> +		  warning (_("Unable to restore previously selected frame."));
> +		  break;
> +		}
> +	    }
> +
> +	  if (silent && (cmd_result == NULL || *cmd_result == 0))
> +	    {
> +	      xfree (cmd_result);
> +	      continue;
> +	    }
> +
>  	  /* Don't use print_stack_frame; if an error() occurs it probably
>  	     means further attempts to backtrace would fail (on the other
>  	     hand, perhaps the code does or could be fixed to make sure
> @@ -1843,6 +1881,13 @@ backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
>  		  break;
>  		}
>  	    }
> +	  if (cmd_result != NULL)
> +	    {
> +	      if (*cmd_result != 0)
> +		printf_filtered ("%s", cmd_result);
> +	      xfree (cmd_result);
> +	    }
> +
>  
>  	  /* Save the last frame to check for error conditions.  */
>  	  trailing = fi;
> @@ -1871,6 +1916,9 @@ backtrace_command (char *arg, int from_tty)
>  {
>    struct cleanup *old_chain = make_cleanup (null_cleanup, NULL);
>    int fulltrace_arg = -1, arglen = 0, argc = 0, no_filters  = -1;
> +  int cmd_arg = -1;
> +  char *cmd = NULL;
> +  int silent_arg = -1;
>    int user_arg = 0;
>  
>    if (arg)
> @@ -1890,29 +1938,39 @@ backtrace_command (char *arg, int from_tty)
>  
>  	  if (no_filters < 0 && subset_compare (argv[i], "no-filters"))
>  	    no_filters = argc;
> +	  else if (fulltrace_arg < 0 && subset_compare (argv[i], "full"))
> +	    fulltrace_arg = argc;
> +	  else if (silent_arg < 0 && subset_compare (argv[i], "/s"))
> +	    silent_arg = argc;
> +	  else if (cmd_arg < 0 && subset_compare ("{", argv[i]))
> +	     {
> +		char *block = strchr(arg, '{');
> +
> +		cmd_arg = argc;
> +		cmd = xmalloc (strlen (block) + 1);
> +		strcpy (cmd, block);
> +		make_cleanup (xfree, cmd);
> +		break;
> +	     }
>  	  else
>  	    {
> -	      if (fulltrace_arg < 0 && subset_compare (argv[i], "full"))
> -		fulltrace_arg = argc;
> -	      else
> -		{
> -		  user_arg++;
> -		  arglen += strlen (argv[i]);
> -		}
> +	      user_arg++;
> +	      arglen += strlen (argv[i]);
>  	    }
>  	  argc++;
>  	}
>        arglen += user_arg;
> -      if (fulltrace_arg >= 0 || no_filters >= 0)
> +      if (fulltrace_arg >= 0 || no_filters >= 0
> +	  || cmd_arg >= 0 || silent_arg > 0)
>  	{
>  	  if (arglen > 0)
>  	    {
>  	      arg = (char *) xmalloc (arglen + 1);
>  	      make_cleanup (xfree, arg);
>  	      arg[0] = 0;
> -	      for (i = 0; i < argc; i++)
> +	      for (i = 0; i < argc && i != cmd_arg; i++)
>  		{
> -		  if (i != fulltrace_arg && i != no_filters)
> +		  if (i != fulltrace_arg && i != no_filters && i != silent_arg)
>  		    {
>  		      strcat (arg, argv[i]);
>  		      strcat (arg, " ");
> @@ -1924,7 +1982,8 @@ backtrace_command (char *arg, int from_tty)
>  	}
>      }
>  
> -  backtrace_command_1 (arg, fulltrace_arg >= 0 /* show_locals */,
> +  backtrace_command_1 (arg, cmd, silent_arg >= 0, /* silent */
> +		       fulltrace_arg >= 0 /* show_locals */,
>  		       no_filters >= 0 /* no frame-filters */, from_tty);
>  
>    do_cleanups (old_chain);
> @@ -2600,7 +2659,12 @@ Print backtrace of all stack frames, or innermost COUNT frames.\n\
>  With a negative argument, print outermost -COUNT frames.\nUse of the \
>  'full' qualifier also prints the values of the local variables.\n\
>  Use of the 'no-filters' qualifier prohibits frame filters from executing\n\
> -on this backtrace.\n"));
> +on this backtrace.\n\
> +After the backtrace arguments and qualifiers, you can specify a block of commands.\n\
> +The block of commands will be executed for each each printed stack frame.\n\
> +Example:    backtrace -5 {/s print i; print j}\n\
> +will print the variables i and j (if they exists) in all printed stack frames.\n\
> +   use    help {    for the syntax of a block of command."));
>    add_com_alias ("bt", "backtrace", class_stack, 0);
>  
>    add_com_alias ("where", "backtrace", class_alias, 0);
> diff --git a/gdb/thread.c b/gdb/thread.c
> index 6d410fb..8fdcfb8 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -1596,6 +1596,7 @@ thread_apply_all_command (char *cmd, int from_tty)
>  {
>    struct cleanup *old_chain;
>    char *saved_cmd;
> +  int silent = 0;
>    int tc;
>    struct thread_array_cleanup ta_cleanup;
>  
> @@ -1607,6 +1608,13 @@ thread_apply_all_command (char *cmd, int from_tty)
>        tp_array_compar_ascending = 1;
>      }
>  
> +  if (cmd != NULL
> +      && check_for_argument (&cmd, "/s", strlen ("/s")))
> +    {
> +      cmd = skip_spaces (cmd);
> +      silent = 1;
> +    }
> +
>    if (cmd == NULL || *cmd == '\000')
>      error (_("Please specify a command following the thread ID list"));
>  
> @@ -1652,11 +1660,23 @@ thread_apply_all_command (char *cmd, int from_tty)
>        for (k = 0; k != i; k++)
>          if (thread_alive (tp_array[k]))
>            {
> +	    char *cmd_result = NULL;
> +
>              switch_to_thread (tp_array[k]->ptid);
> -            printf_filtered (_("\nThread %d (%s):\n"), 
> -			     tp_array[k]->num,
> -			     target_pid_to_str (inferior_ptid));
> -            execute_command (cmd, from_tty);
> +	    if (silent)
> +	      cmd_result = execute_command_to_string (cmd, from_tty);
> +	    if (!silent || (cmd_result != NULL && *cmd_result != 0))
> +	      printf_filtered (_("\nThread %d (%s):\n"),
> +			       tp_array[k]->num,
> +			       target_pid_to_str (inferior_ptid));
> +	    if (cmd_result)
> +	      {
> +		if (*cmd_result != 0)
> +		  printf_filtered ("%s", cmd_result);
> +		xfree (cmd_result);
> +	      }
> +	    else
> +	      execute_command (cmd, from_tty);
>  
>              /* Restore exact command used previously.  */
>              strcpy (cmd, saved_cmd);
> @@ -1677,7 +1697,9 @@ thread_apply_command (char *tidlist, int from_tty)
>    if (tidlist == NULL || *tidlist == '\000')
>      error (_("Please specify a thread ID list"));
>  
> -  for (cmd = tidlist; *cmd != '\000' && !isalpha (*cmd); cmd++);
> +  for (cmd = tidlist;
> +       *cmd != '\000' && !isalpha (*cmd) && *cmd != '{';
> +       cmd++);
>  
>    if (*cmd == '\000')
>      error (_("Please specify a command following the thread ID list"));
> diff --git a/gdb/top.c b/gdb/top.c
> index d1e2271..b553028 100644
> --- a/gdb/top.c
> +++ b/gdb/top.c
> @@ -534,6 +534,151 @@ execute_command_to_string (char *p, int from_tty)
>    return retval;
>  }
>  
> +static void
> +execute_one_block_command (char *cmd, int from_tty,
> +			   int continue_on_failure, int silent_failure)
> +{
> +  if (continue_on_failure)
> +    {
> +      TRY
> +	{
> +	  execute_command (cmd, from_tty);
> +	}
> +      CATCH (e, RETURN_MASK_ERROR)
> +	{
> +	  if (!silent_failure)
> +	    exception_print (gdb_stderr, e);
> +	}
> +      END_CATCH
> +	}
> +  else
> +    execute_command (cmd, from_tty);
> +}
> +
> +/* Parses in cmds optional /CONTROL, then executes each command part
> +   of the block. Returns the position in cmds after the block end.  */
> +static void
> +commands_block_command (char *cmds, int from_tty)
> +{
> +  int continue_on_failure = 0;
> +  int silent_failure = 0;
> +  int level = 0;
> +  char *cmd_end = NULL;
> +
> +#if 0
> +#define XX(msg) printf("%s cmds%s <%s> %p cmd_end <%s> %p\n", \
> +		       msg,					\
> +		       silent_failure ? "/s" :			\
> +		       continue_on_failure ? "/c" : "",		\
> +		       cmds, cmds, cmd_end, cmd_end)
> +#else
> +#define XX(msg)
> +#endif
> +
> +  cmds = skip_spaces (cmds);
> +
> +  if (cmds == NULL || *cmds == 0)
> +    error (_("Please specify one or more commands separated by ;,\n"
> +	     "optionally followed by a block end }"));
> +
> +  /* Parses the optional /CONTROL.  */
> +  if (*cmds == '/')
> +    {
> +      cmds++;
> +      while (*cmds) {
> +	if (*cmds == ' ')
> +	  break;
> +	else if (*cmds == 's')
> +	  continue_on_failure = silent_failure = 1;
> +	else if (*cmds == 'c')
> +	  continue_on_failure = 1;
> +	else
> +	  error (_("Invalid commands block control flag %c"), *cmds);
> +	cmds++;
> +      }
> +    }
> +
> +  cmds = skip_spaces (cmds);
> +  cmd_end = cmds;
> +  level = 0;
> +  XX("enter");
> +  while (*cmd_end)
> +    {
> +      if (*cmd_end == '\\'
> +	  && (*(cmd_end+1) == '{'
> +	      || *(cmd_end+1) == '}'
> +	      || *(cmd_end+1) == ';'))
> +	{
> +	  char *p, *q;
> +
> +	  p = cmd_end;
> +	  q = cmd_end+1;
> +	  while (*q)
> +	    *p++ = *q++;
> +	  cmd_end++;
> +	}
> +      else if (*cmd_end == '{')
> +	{
> +	  /* Found the begin of a nested block.  */
> +
> +	  /* Execute last command of including block, if any.  */
> +	  if (cmds <= cmd_end)
> +	    {
> +	      *cmd_end = 0;
> +	      XX("prev block lastcmd");
> +	      execute_one_block_command (cmds, from_tty,
> +					 continue_on_failure, silent_failure);
> +	      *cmd_end = '{';
> +	    }
> +
> +	  /* Search for the nested block end: either a } or end of cmds.  */
> +	  cmds = cmd_end;
> +	  while (*cmd_end) {
> +	    if (*cmd_end == '{')
> +	      level++;
> +	    else if (*cmd_end == '}')
> +	      level--;
> +	    if (level == 0)
> +	      break;
> +	    cmd_end++;
> +	  }
> +
> +	  /* recursively executes the block.  */
> +	  *cmd_end = 0;
> +	  XX("block");
> +	  execute_one_block_command (cmds, from_tty,
> +				     continue_on_failure, silent_failure);
> +	  if (level == 0)
> +	    cmd_end++;
> +	  cmds = cmd_end;
> +	}
> +      else if ((*cmd_end == ';' || *cmd_end == '}') && level == 0)
> +	{
> +	  /*  When encountering a command terminator or a block end,
> +	      executes this command.  Note : the block end comparison
> +	      is needed to execute the last command of a block, if
> +	      this command is not terminated by ;.  */
> +	  *cmd_end = 0;
> +	  XX("cmd");
> +	  execute_one_block_command (cmds, from_tty,
> +				     continue_on_failure, silent_failure);
> +	  cmd_end++;
> +	  cmds = cmd_end;
> +	}
> +      else
> +	cmd_end++;
> +    }
> +
> +  /* execute last command of this block, if any */
> +  if (cmds <= cmd_end && *cmds)
> +    {
> +      XX("lastcmd");
> +      execute_one_block_command (cmds, from_tty,
> +				 continue_on_failure, silent_failure);
> +    }
> +#undef XX
> +}
> +
>  /* Read commands from `instream' and execute them
>     until end of file or error reading instream.  */
>  
> @@ -1942,6 +2087,17 @@ Don't repeat this command.\nPrimarily \
>  used inside of user-defined commands that should not be repeated when\n\
>  hitting return."));
>  
> +  add_com ("{", class_support,
> +	   commands_block_command, _("\
> +Executes a block of commands : {/CONTROL CMD1; CMD2; ... }.\n\
> +Commands are separated by the character ';'.\n\
> +Nested blocks can be terminated by the character '}'.\n\
> +By default, the execution of a block stops if a CMD fails.\n\
> +/CONTROL allows to control the execution in case of a CMD failure:\n\
> +  /c indicates to report the error and continue the block execution.\n\
> +  /s indicates to silently continue the block execution.\n\
> +You can use '\\' to escape '{', '}' and ';'."));
> +
>    add_setshow_boolean_cmd ("editing", class_support,
>  			   &async_command_editing_p, _("\
>  Set editing of command lines as they are typed."), _("\
> 
> 
> 
>
diff mbox

Patch

diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c
index cab2336..615e543 100644
--- a/gdb/cli/cli-decode.c
+++ b/gdb/cli/cli-decode.c
@@ -1255,6 +1255,11 @@  find_command_name_length (const char *text)
   if (*p == '!')
     return 1;
 
+  /* Similarly, recognize '{' as a single character command so that
+     a command block works as expected.  */
+  if (*p == '{')
+    return 1;
+
   while (isalnum (*p) || *p == '-' || *p == '_'
 	 /* Characters used by TUI specific commands.  */
 	 || *p == '+' || *p == '<' || *p == '>' || *p == '$')
diff --git a/gdb/stack.c b/gdb/stack.c
index b825bdf..d8d9791 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1712,10 +1712,14 @@  frame_info (char *addr_exp, int from_tty)
 }
 
 /* Print briefly all stack frames or just the innermost COUNT_EXP
-   frames.  */
+   frames.  If cmd != NULL, executes cmd in the context of each
+   printed stack frame.  If silent, only print stack frames
+   for which cmd has produced some output.  */
 
 static void
-backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
+backtrace_command_1 (char *count_exp,
+		     char *cmd, int silent,
+		     int show_locals, int no_filters,
 		     int from_tty)
 {
   struct frame_info *fi;
@@ -1820,8 +1824,42 @@  backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
     {
       for (i = 0, fi = trailing; fi && count--; i++, fi = get_prev_frame (fi))
 	{
+	  char *cmd_result = NULL;
+
 	  QUIT;
 
+	  if (cmd != NULL)
+	    {
+	      char *saved_cmd;
+	      struct frame_id frame_id = get_frame_id (fi);
+
+	      /* Save a copy of the command in case it is clobbered by
+		 execute_command.  */
+	      saved_cmd = xstrdup (cmd);
+	      make_cleanup (xfree, saved_cmd);
+
+	      select_frame (fi);
+	      cmd_result = execute_command_to_string (cmd, from_tty);
+
+	      /* Restore exact command used previously.  */
+	      strcpy (cmd, saved_cmd);
+
+	      /* execute_command (might) invalidates FI.  */
+	      fi = frame_find_by_id (frame_id);
+	      if (fi == NULL)
+		{
+		  trailing = NULL;
+		  warning (_("Unable to restore previously selected frame."));
+		  break;
+		}
+	    }
+
+	  if (silent && (cmd_result == NULL || *cmd_result == 0))
+	    {
+	      xfree (cmd_result);
+	      continue;
+	    }
+
 	  /* Don't use print_stack_frame; if an error() occurs it probably
 	     means further attempts to backtrace would fail (on the other
 	     hand, perhaps the code does or could be fixed to make sure
@@ -1843,6 +1881,13 @@  backtrace_command_1 (char *count_exp, int show_locals, int no_filters,
 		  break;
 		}
 	    }
+	  if (cmd_result != NULL)
+	    {
+	      if (*cmd_result != 0)
+		printf_filtered ("%s", cmd_result);
+	      xfree (cmd_result);
+	    }
+
 
 	  /* Save the last frame to check for error conditions.  */
 	  trailing = fi;
@@ -1871,6 +1916,9 @@  backtrace_command (char *arg, int from_tty)
 {
   struct cleanup *old_chain = make_cleanup (null_cleanup, NULL);
   int fulltrace_arg = -1, arglen = 0, argc = 0, no_filters  = -1;
+  int cmd_arg = -1;
+  char *cmd = NULL;
+  int silent_arg = -1;
   int user_arg = 0;
 
   if (arg)
@@ -1890,29 +1938,39 @@  backtrace_command (char *arg, int from_tty)
 
 	  if (no_filters < 0 && subset_compare (argv[i], "no-filters"))
 	    no_filters = argc;
+	  else if (fulltrace_arg < 0 && subset_compare (argv[i], "full"))
+	    fulltrace_arg = argc;
+	  else if (silent_arg < 0 && subset_compare (argv[i], "/s"))
+	    silent_arg = argc;
+	  else if (cmd_arg < 0 && subset_compare ("{", argv[i]))
+	     {
+		char *block = strchr(arg, '{');
+
+		cmd_arg = argc;
+		cmd = xmalloc (strlen (block) + 1);
+		strcpy (cmd, block);
+		make_cleanup (xfree, cmd);
+		break;
+	     }
 	  else
 	    {
-	      if (fulltrace_arg < 0 && subset_compare (argv[i], "full"))
-		fulltrace_arg = argc;
-	      else
-		{
-		  user_arg++;
-		  arglen += strlen (argv[i]);
-		}
+	      user_arg++;
+	      arglen += strlen (argv[i]);
 	    }
 	  argc++;
 	}
       arglen += user_arg;
-      if (fulltrace_arg >= 0 || no_filters >= 0)
+      if (fulltrace_arg >= 0 || no_filters >= 0
+	  || cmd_arg >= 0 || silent_arg > 0)
 	{
 	  if (arglen > 0)
 	    {
 	      arg = (char *) xmalloc (arglen + 1);
 	      make_cleanup (xfree, arg);
 	      arg[0] = 0;
-	      for (i = 0; i < argc; i++)
+	      for (i = 0; i < argc && i != cmd_arg; i++)
 		{
-		  if (i != fulltrace_arg && i != no_filters)
+		  if (i != fulltrace_arg && i != no_filters && i != silent_arg)
 		    {
 		      strcat (arg, argv[i]);
 		      strcat (arg, " ");
@@ -1924,7 +1982,8 @@  backtrace_command (char *arg, int from_tty)
 	}
     }
 
-  backtrace_command_1 (arg, fulltrace_arg >= 0 /* show_locals */,
+  backtrace_command_1 (arg, cmd, silent_arg >= 0, /* silent */
+		       fulltrace_arg >= 0 /* show_locals */,
 		       no_filters >= 0 /* no frame-filters */, from_tty);
 
   do_cleanups (old_chain);
@@ -2600,7 +2659,12 @@  Print backtrace of all stack frames, or innermost COUNT frames.\n\
 With a negative argument, print outermost -COUNT frames.\nUse of the \
 'full' qualifier also prints the values of the local variables.\n\
 Use of the 'no-filters' qualifier prohibits frame filters from executing\n\
-on this backtrace.\n"));
+on this backtrace.\n\
+After the backtrace arguments and qualifiers, you can specify a block of commands.\n\
+The block of commands will be executed for each each printed stack frame.\n\
+Example:    backtrace -5 {/s print i; print j}\n\
+will print the variables i and j (if they exists) in all printed stack frames.\n\
+   use    help {    for the syntax of a block of command."));
   add_com_alias ("bt", "backtrace", class_stack, 0);
 
   add_com_alias ("where", "backtrace", class_alias, 0);
diff --git a/gdb/thread.c b/gdb/thread.c
index 6d410fb..8fdcfb8 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -1596,6 +1596,7 @@  thread_apply_all_command (char *cmd, int from_tty)
 {
   struct cleanup *old_chain;
   char *saved_cmd;
+  int silent = 0;
   int tc;
   struct thread_array_cleanup ta_cleanup;
 
@@ -1607,6 +1608,13 @@  thread_apply_all_command (char *cmd, int from_tty)
       tp_array_compar_ascending = 1;
     }
 
+  if (cmd != NULL
+      && check_for_argument (&cmd, "/s", strlen ("/s")))
+    {
+      cmd = skip_spaces (cmd);
+      silent = 1;
+    }
+
   if (cmd == NULL || *cmd == '\000')
     error (_("Please specify a command following the thread ID list"));
 
@@ -1652,11 +1660,23 @@  thread_apply_all_command (char *cmd, int from_tty)
       for (k = 0; k != i; k++)
         if (thread_alive (tp_array[k]))
           {
+	    char *cmd_result = NULL;
+
             switch_to_thread (tp_array[k]->ptid);
-            printf_filtered (_("\nThread %d (%s):\n"), 
-			     tp_array[k]->num,
-			     target_pid_to_str (inferior_ptid));
-            execute_command (cmd, from_tty);
+	    if (silent)
+	      cmd_result = execute_command_to_string (cmd, from_tty);
+	    if (!silent || (cmd_result != NULL && *cmd_result != 0))
+	      printf_filtered (_("\nThread %d (%s):\n"),
+			       tp_array[k]->num,
+			       target_pid_to_str (inferior_ptid));
+	    if (cmd_result)
+	      {
+		if (*cmd_result != 0)
+		  printf_filtered ("%s", cmd_result);
+		xfree (cmd_result);
+	      }
+	    else
+	      execute_command (cmd, from_tty);
 
             /* Restore exact command used previously.  */
             strcpy (cmd, saved_cmd);
@@ -1677,7 +1697,9 @@  thread_apply_command (char *tidlist, int from_tty)
   if (tidlist == NULL || *tidlist == '\000')
     error (_("Please specify a thread ID list"));
 
-  for (cmd = tidlist; *cmd != '\000' && !isalpha (*cmd); cmd++);
+  for (cmd = tidlist;
+       *cmd != '\000' && !isalpha (*cmd) && *cmd != '{';
+       cmd++);
 
   if (*cmd == '\000')
     error (_("Please specify a command following the thread ID list"));
diff --git a/gdb/top.c b/gdb/top.c
index d1e2271..b553028 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -534,6 +534,151 @@  execute_command_to_string (char *p, int from_tty)
   return retval;
 }
 
+static void
+execute_one_block_command (char *cmd, int from_tty,
+			   int continue_on_failure, int silent_failure)
+{
+  if (continue_on_failure)
+    {
+      TRY
+	{
+	  execute_command (cmd, from_tty);
+	}
+      CATCH (e, RETURN_MASK_ERROR)
+	{
+	  if (!silent_failure)
+	    exception_print (gdb_stderr, e);
+	}
+      END_CATCH
+	}
+  else
+    execute_command (cmd, from_tty);
+}
+
+/* Parses in cmds optional /CONTROL, then executes each command part
+   of the block. Returns the position in cmds after the block end.  */
+static void
+commands_block_command (char *cmds, int from_tty)
+{
+  int continue_on_failure = 0;
+  int silent_failure = 0;
+  int level = 0;
+  char *cmd_end = NULL;
+
+#if 0
+#define XX(msg) printf("%s cmds%s <%s> %p cmd_end <%s> %p\n", \
+		       msg,					\
+		       silent_failure ? "/s" :			\
+		       continue_on_failure ? "/c" : "",		\
+		       cmds, cmds, cmd_end, cmd_end)
+#else
+#define XX(msg)
+#endif
+
+  cmds = skip_spaces (cmds);
+
+  if (cmds == NULL || *cmds == 0)
+    error (_("Please specify one or more commands separated by ;,\n"
+	     "optionally followed by a block end }"));
+
+  /* Parses the optional /CONTROL.  */
+  if (*cmds == '/')
+    {
+      cmds++;
+      while (*cmds) {
+	if (*cmds == ' ')
+	  break;
+	else if (*cmds == 's')
+	  continue_on_failure = silent_failure = 1;
+	else if (*cmds == 'c')
+	  continue_on_failure = 1;
+	else
+	  error (_("Invalid commands block control flag %c"), *cmds);
+	cmds++;
+      }
+    }
+
+  cmds = skip_spaces (cmds);
+  cmd_end = cmds;
+  level = 0;
+  XX("enter");
+  while (*cmd_end)
+    {
+      if (*cmd_end == '\\'
+	  && (*(cmd_end+1) == '{'
+	      || *(cmd_end+1) == '}'
+	      || *(cmd_end+1) == ';'))
+	{
+	  char *p, *q;
+
+	  p = cmd_end;
+	  q = cmd_end+1;
+	  while (*q)
+	    *p++ = *q++;
+	  cmd_end++;
+	}
+      else if (*cmd_end == '{')
+	{
+	  /* Found the begin of a nested block.  */
+
+	  /* Execute last command of including block, if any.  */
+	  if (cmds <= cmd_end)
+	    {
+	      *cmd_end = 0;
+	      XX("prev block lastcmd");
+	      execute_one_block_command (cmds, from_tty,
+					 continue_on_failure, silent_failure);
+	      *cmd_end = '{';
+	    }
+
+	  /* Search for the nested block end: either a } or end of cmds.  */
+	  cmds = cmd_end;
+	  while (*cmd_end) {
+	    if (*cmd_end == '{')
+	      level++;
+	    else if (*cmd_end == '}')
+	      level--;
+	    if (level == 0)
+	      break;
+	    cmd_end++;
+	  }
+
+	  /* recursively executes the block.  */
+	  *cmd_end = 0;
+	  XX("block");
+	  execute_one_block_command (cmds, from_tty,
+				     continue_on_failure, silent_failure);
+	  if (level == 0)
+	    cmd_end++;
+	  cmds = cmd_end;
+	}
+      else if ((*cmd_end == ';' || *cmd_end == '}') && level == 0)
+	{
+	  /*  When encountering a command terminator or a block end,
+	      executes this command.  Note : the block end comparison
+	      is needed to execute the last command of a block, if
+	      this command is not terminated by ;.  */
+	  *cmd_end = 0;
+	  XX("cmd");
+	  execute_one_block_command (cmds, from_tty,
+				     continue_on_failure, silent_failure);
+	  cmd_end++;
+	  cmds = cmd_end;
+	}
+      else
+	cmd_end++;
+    }
+
+  /* execute last command of this block, if any */
+  if (cmds <= cmd_end && *cmds)
+    {
+      XX("lastcmd");
+      execute_one_block_command (cmds, from_tty,
+				 continue_on_failure, silent_failure);
+    }
+#undef XX
+}
+
 /* Read commands from `instream' and execute them
    until end of file or error reading instream.  */
 
@@ -1942,6 +2087,17 @@  Don't repeat this command.\nPrimarily \
 used inside of user-defined commands that should not be repeated when\n\
 hitting return."));
 
+  add_com ("{", class_support,
+	   commands_block_command, _("\
+Executes a block of commands : {/CONTROL CMD1; CMD2; ... }.\n\
+Commands are separated by the character ';'.\n\
+Nested blocks can be terminated by the character '}'.\n\
+By default, the execution of a block stops if a CMD fails.\n\
+/CONTROL allows to control the execution in case of a CMD failure:\n\
+  /c indicates to report the error and continue the block execution.\n\
+  /s indicates to silently continue the block execution.\n\
+You can use '\\' to escape '{', '}' and ';'."));
+
   add_setshow_boolean_cmd ("editing", class_support,
 			   &async_command_editing_p, _("\
 Set editing of command lines as they are typed."), _("\