From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 64259 invoked by alias); 3 Dec 2015 22:28:41 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Received: (qmail 64232 invoked by uid 89); 3 Dec 2015 22:28:39 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-1.6 required=5.0 tests=AWL,BAYES_00,RCVD_IN_DNSWL_LOW,SPF_PASS autolearn=ham version=3.3.2 X-HELO: mailsec101.isp.belgacom.be Received: from mailsec101.isp.belgacom.be (HELO mailsec101.isp.belgacom.be) (195.238.20.97) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Thu, 03 Dec 2015 22:28:36 +0000 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: A2CQBgBJwWBW/5DT81Fegzq/EIFugzyCUgKBTTsSAQEBAQEBAYEKhDQBAQEDASMELygLCAMYAgImAgI5HgYtiA0MsRORRRlohVOEfYd3gUQFlmGNPIFbhEODJpMpKAc0ghEdgVc9hiMBAQE Received: from 144.211-243-81.adsl-dyn.isp.belgacom.be (HELO soleil) ([81.243.211.144]) by relay.skynet.be with ESMTP/TLS/AES128-GCM-SHA256; 03 Dec 2015 23:28:32 +0100 Message-ID: <1449181812.8195.1.camel@skynet.be> Subject: Re: RFC: block of commands From: Philippe Waroquiers To: gdb-patches Date: Thu, 03 Dec 2015 22:28:00 -0000 In-Reply-To: <1448131372.2153.17.camel@skynet.be> References: <1448131372.2153.17.camel@skynet.be> Content-Type: text/plain; charset="UTF-8" Mime-Version: 1.0 Content-Transfer-Encoding: 7bit X-IsSubscribed: yes X-SW-Source: 2015-12/txt/msg00060.txt.bz2 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."), _("\ > > > > >