* Re: RFC: block of commands
@ 2016-01-21 1:29 Doug Evans
2016-01-22 21:47 ` Philippe Waroquiers
0 siblings, 1 reply; 10+ messages in thread
From: Doug Evans @ 2016-01-21 1:29 UTC (permalink / raw)
To: Philippe Waroquiers; +Cc: gdb-patches
Philippe Waroquiers writes:
> On Mon, 2016-01-11 at 21:40 +0000, Doug Evans wrote:
> > Heya.
> >
> > One feature I've always wanted gdb's cli to have is the
> > ability to catch errors and be able to continue.
> > There's always a tension of whether to add things to the cli
> > or just say "do it in python". gdb scripts are still useful
> > and thus sometimes I think it's ok to extend the cli, such
> > as in this case.
> > Thus instead of /c in this patch I'd prefer something
> > along the lines of "try ... end".
> > ["end" is gdb's canonical "trailing }", and I'm all for
> > Consistency Is Good here.]
> > The actual details and semantics of "try" I would
> > leave to a separate thread. For one, I'd have to think
> > about it some more :-), but I would support someone
> > wanting to go down that path.
> >
> > OTOH, trying to extend the cli's syntax with { and ; is not
> > something I can support, at least not without more discussion.
> > This example:
> >
> > thread apply all { set $prevsp = $sp; bt \{ p $ebp - $prevsp\; set
> > $prevsp = $sp \} }
> > => print the size of each frame
> >
> > suggests things could be better; having to escape braces within
> > subcommands feels excessively error prone.
> > gdb has one line per command, and changing that
> > may make things too unwieldly.
> The { (and have the terminating } optional) was chosen in order
> to make the interactive typing of a block of command faster/easier.
> I agree that this new syntax is very different of the
> 'usual gdb multi-lines terminated by end'.
>
> However, multi-lines with end means the user cannot simply type e.g.
> thread apply all { p i; p j}
> Instead, the user must type:
> define pij
> p i
> p j
> end
> thread apply all pij
> And after, if the user wants to also print k,
> then the pij sequence must be retyped (I do not know a way
> to change/edit/complete a user defined command).
Yeah. I recognize the difficulty.
But adding {...; ...} on top of what's there now
is going to be a slippery slope I fear.
[maybe not, but we're adding syntax to what
has until now been pretty free form]
I want to make sure that if we add this,
we don't some day end up regretting it.
Note that at least within emacs I can repeat/edit
multi-line commands by going back through the history.
Plus one can redefine user-defined commands
from the keyboard. So it's possible, if tedious.
> An alternative might be to make the terminating } mandatory.
> Then I think we could get rid of the { and } escaping
I'd prefer to be strict (e.g., require }), at least for now.
Once we add { ...; ... }, someone is going to want to extend it,
and then we've got if/while/etc. that can all be done as one-liners.
[If we're going to make interactive use easier, why can't
if/while/etc. be done as one-liners? It feels like if a
compelling argument can't be made to allow them to be one-liners,
then supporting one-liners in a more restricted fashion
isn't very compelling and maybe we should just punt.]
Let's think this through.
> So, the question is how much we want to make the block of commands
> easy to use interactively ?
>
> Maybe it is not that unusual to have 2 concepts
> of 'list of commands' :
> one command per line, with some 'begin/end' keywords
> several commands per lines, separated by ;
> (i.e. similar to what a shell is providing with if/fi and ;).
>
> The ; separated list versus multi-lines is clearly the main
> point of discussion.
>
> Note that if ever the {}; idea survives, we have to take care
> about parsing of existing scripts that would do e.g.
> echo { is an open bracket
> Not too sure it will be easy to make the {}; idea fully backward
> compatible :(
That's going to be a tricky part, yeah.
Existing "echo {" may not be so bad, as echo doesn't take a command
as an argument. While less than ideal, we could defer parsing
{ ... } until we're parsing a command that takes a command as
an argument (either literally, as in "thread apply ...",
or conceptually, as in "if expr ...").
Of course, extending "if/while" like this would be problematic if
a language allowed { in an expression.
Another thought is: If we extend this to if/while/etc.
then it'd be odd to not be able to terminate a multi-line "if"
with }, except that there is no opening { in a multi-line "if".
So does that mean { ... } is restricted to one line?
Probably, but it may trip people up from time to time.
Another thought: What does this do?
if a == b { echo foo }
If we don't enforce a trailing } then is that "echo foo" or "echo foo }"?
Maybe a good example to suggest we should indeed require a trailing }?
Similarly with ;
if a == b { echo foo; }
These questions apply even if we don't extend "if":
thread apply all { echo foo; }
> > There are still
> > macros so one could still do "thread apply all print-frame-size",
> > and put the guts of frame size computation in a macro named
> > print-frame-size.
> True, but that makes the interactive use and editing
> not so easy (cfr above).
Yep.
This is a significant addition,
I'm just asking for a thorough vetting of it.
Deciding how to parse embedded {/;/} is probably
the important part (as you've mentioned).
E.g., if a == b { if c == d { echo foo; }; echo bar }
or
thread apply all { frame apply all { echo \{foo\}; printf "\{foo\}"; } }
We have to find the outer } so we know what command to
execute for each thread, but in order to do that we
have to do some parsing of the inner command in order
to handle the nesting.
Plus, as you've mentioned, we'll need some kind of escaping;
when we run the "frame apply all" command is it:
echo {foo}; printf "\{foo\}"
?
Since this is new syntax, we're free to define things
however we like, within reason.
> > Also, I'd prefer to have generally composable commands
> > over extending individual commands.
> > E.g., one common thing to do with "thread apply" is backtrace,
> > as in "thread apply all bt". I like that much better than
> > if we had extended backtrace to work on each thread.
> >
> > Following "Consistency Is Good", and given that we have
> > "thread apply", for your "bt {/c p i" case how about
> > something like:
> >
> > frame apply all foo
> Yes, that looks better than adding an optional command to
> backtrace.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: block of commands
2016-01-21 1:29 RFC: block of commands Doug Evans
@ 2016-01-22 21:47 ` Philippe Waroquiers
2016-01-24 20:03 ` Doug Evans
0 siblings, 1 reply; 10+ messages in thread
From: Philippe Waroquiers @ 2016-01-22 21:47 UTC (permalink / raw)
To: Doug Evans; +Cc: gdb-patches
I think that effectively, the principles you suggest below
should allow to make a reasonable syntax, and no backward
incompatibility.
So basically:
* trailing } is mandatory
* inside a block of commands, { } and ; must be escaped
* a command block is limited to be on a single line
I guess multiple lines will be ok if each line is terminated
by a \.
The above principles should also allow to extend if/while/...
to have 'one line' if/while/...
I also think that 'one line if' will allow to do things such as:
thread apply all if $ebp -$esp > 1000000 { echo big frame in this thread }
(today, trying to use an 'if' in thread apply gives a quite strange
behaviour : the rest of the 'if' command is read during each execution
for each thread, which is not very user friendly :).
I will work based on the above idea, and resubmit a draft patch
but that will for sure have to wait after FOSDEM, which
will already consume the night and week-end free time :).
Thanks for the comments/help/nice suggestions
Philippe
On Thu, 2016-01-21 at 01:29 +0000, Doug Evans wrote:
> Philippe Waroquiers writes:
> > On Mon, 2016-01-11 at 21:40 +0000, Doug Evans wrote:
> > > Heya.
> > >
> > > One feature I've always wanted gdb's cli to have is the
> > > ability to catch errors and be able to continue.
> > > There's always a tension of whether to add things to the cli
> > > or just say "do it in python". gdb scripts are still useful
> > > and thus sometimes I think it's ok to extend the cli, such
> > > as in this case.
> > > Thus instead of /c in this patch I'd prefer something
> > > along the lines of "try ... end".
> > > ["end" is gdb's canonical "trailing }", and I'm all for
> > > Consistency Is Good here.]
> > > The actual details and semantics of "try" I would
> > > leave to a separate thread. For one, I'd have to think
> > > about it some more :-), but I would support someone
> > > wanting to go down that path.
> > >
> > > OTOH, trying to extend the cli's syntax with { and ; is not
> > > something I can support, at least not without more discussion.
> > > This example:
> > >
> > > thread apply all { set $prevsp = $sp; bt \{ p $ebp - $prevsp\; set
> > > $prevsp = $sp \} }
> > > => print the size of each frame
> > >
> > > suggests things could be better; having to escape braces within
> > > subcommands feels excessively error prone.
> > > gdb has one line per command, and changing that
> > > may make things too unwieldly.
> > The { (and have the terminating } optional) was chosen in order
> > to make the interactive typing of a block of command faster/easier.
> > I agree that this new syntax is very different of the
> > 'usual gdb multi-lines terminated by end'.
> >
> > However, multi-lines with end means the user cannot simply type e.g.
> > thread apply all { p i; p j}
> > Instead, the user must type:
> > define pij
> > p i
> > p j
> > end
> > thread apply all pij
> > And after, if the user wants to also print k,
> > then the pij sequence must be retyped (I do not know a way
> > to change/edit/complete a user defined command).
>
> Yeah. I recognize the difficulty.
> But adding {...; ...} on top of what's there now
> is going to be a slippery slope I fear.
> [maybe not, but we're adding syntax to what
> has until now been pretty free form]
> I want to make sure that if we add this,
> we don't some day end up regretting it.
>
> Note that at least within emacs I can repeat/edit
> multi-line commands by going back through the history.
> Plus one can redefine user-defined commands
> from the keyboard. So it's possible, if tedious.
>
> > An alternative might be to make the terminating } mandatory.
> > Then I think we could get rid of the { and } escaping
>
> I'd prefer to be strict (e.g., require }), at least for now.
> Once we add { ...; ... }, someone is going to want to extend it,
> and then we've got if/while/etc. that can all be done as one-liners.
> [If we're going to make interactive use easier, why can't
> if/while/etc. be done as one-liners? It feels like if a
> compelling argument can't be made to allow them to be one-liners,
> then supporting one-liners in a more restricted fashion
> isn't very compelling and maybe we should just punt.]
> Let's think this through.
>
> > So, the question is how much we want to make the block of commands
> > easy to use interactively ?
> >
> > Maybe it is not that unusual to have 2 concepts
> > of 'list of commands' :
> > one command per line, with some 'begin/end' keywords
> > several commands per lines, separated by ;
> > (i.e. similar to what a shell is providing with if/fi and ;).
> >
> > The ; separated list versus multi-lines is clearly the main
> > point of discussion.
> >
> > Note that if ever the {}; idea survives, we have to take care
> > about parsing of existing scripts that would do e.g.
> > echo { is an open bracket
> > Not too sure it will be easy to make the {}; idea fully backward
> > compatible :(
>
> That's going to be a tricky part, yeah.
>
> Existing "echo {" may not be so bad, as echo doesn't take a command
> as an argument. While less than ideal, we could defer parsing
> { ... } until we're parsing a command that takes a command as
> an argument (either literally, as in "thread apply ...",
> or conceptually, as in "if expr ...").
> Of course, extending "if/while" like this would be problematic if
> a language allowed { in an expression.
>
> Another thought is: If we extend this to if/while/etc.
> then it'd be odd to not be able to terminate a multi-line "if"
> with }, except that there is no opening { in a multi-line "if".
> So does that mean { ... } is restricted to one line?
> Probably, but it may trip people up from time to time.
>
> Another thought: What does this do?
>
> if a == b { echo foo }
>
> If we don't enforce a trailing } then is that "echo foo" or "echo foo }"?
> Maybe a good example to suggest we should indeed require a trailing }?
>
> Similarly with ;
>
> if a == b { echo foo; }
>
> These questions apply even if we don't extend "if":
>
> thread apply all { echo foo; }
>
> > > There are still
> > > macros so one could still do "thread apply all print-frame-size",
> > > and put the guts of frame size computation in a macro named
> > > print-frame-size.
> > True, but that makes the interactive use and editing
> > not so easy (cfr above).
>
> Yep.
> This is a significant addition,
> I'm just asking for a thorough vetting of it.
>
> Deciding how to parse embedded {/;/} is probably
> the important part (as you've mentioned).
>
> E.g., if a == b { if c == d { echo foo; }; echo bar }
>
> or
>
> thread apply all { frame apply all { echo \{foo\}; printf "\{foo\}"; } }
>
> We have to find the outer } so we know what command to
> execute for each thread, but in order to do that we
> have to do some parsing of the inner command in order
> to handle the nesting.
> Plus, as you've mentioned, we'll need some kind of escaping;
> when we run the "frame apply all" command is it:
> echo {foo}; printf "\{foo\}"
> ?
>
> Since this is new syntax, we're free to define things
> however we like, within reason.
>
> > > Also, I'd prefer to have generally composable commands
> > > over extending individual commands.
> > > E.g., one common thing to do with "thread apply" is backtrace,
> > > as in "thread apply all bt". I like that much better than
> > > if we had extended backtrace to work on each thread.
> > >
> > > Following "Consistency Is Good", and given that we have
> > > "thread apply", for your "bt {/c p i" case how about
> > > something like:
> > >
> > > frame apply all foo
> > Yes, that looks better than adding an optional command to
> > backtrace.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: block of commands
2016-01-22 21:47 ` Philippe Waroquiers
@ 2016-01-24 20:03 ` Doug Evans
2016-01-24 22:23 ` Philippe Waroquiers
0 siblings, 1 reply; 10+ messages in thread
From: Doug Evans @ 2016-01-24 20:03 UTC (permalink / raw)
To: Philippe Waroquiers; +Cc: gdb-patches
On Fri, Jan 22, 2016 at 1:49 PM, Philippe Waroquiers
<philippe.waroquiers@skynet.be> wrote:
>
>
> I think that effectively, the principles you suggest below
> should allow to make a reasonable syntax, and no backward
> incompatibility.
> So basically:
> * trailing } is mandatory
> * inside a block of commands, { } and ; must be escaped
> * a command block is limited to be on a single line
> I guess multiple lines will be ok if each line is terminated
> by a \.
>
> The above principles should also allow to extend if/while/...
> to have 'one line' if/while/...
>
> I also think that 'one line if' will allow to do things such as:
>
> thread apply all if $ebp -$esp > 1000000 { echo big frame in this thread }
>
> (today, trying to use an 'if' in thread apply gives a quite strange
> behaviour : the rest of the 'if' command is read during each execution
> for each thread, which is not very user friendly :).
>
> I will work based on the above idea, and resubmit a draft patch
> but that will for sure have to wait after FOSDEM, which
> will already consume the night and week-end free time :).
>
> Thanks for the comments/help/nice suggestions
>
Hi.
Sounds like a plan, but in an effort to make your time
as productive as I can I would suggest first trying to come
up with syntax that the community can accept.
Among the questions we need to answer:
- when/how to escape { } ;
(what I gave was a strawman, I didn't dig deep
into whether there are any gotchas)
- what to do with { } in expressions,
e.g., "if ({1,2,3})[2] == 3 { echo foo }"
- ???
P.S. Hope you enjoy FOSDEM!
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: block of commands
2016-01-24 20:03 ` Doug Evans
@ 2016-01-24 22:23 ` Philippe Waroquiers
0 siblings, 0 replies; 10+ messages in thread
From: Philippe Waroquiers @ 2016-01-24 22:23 UTC (permalink / raw)
To: Doug Evans; +Cc: gdb-patches
On Sun, 2016-01-24 at 12:03 -0800, Doug Evans wrote:
> On Fri, Jan 22, 2016 at 1:49 PM, Philippe Waroquiers
> <philippe.waroquiers@skynet.be> wrote:
> >
> >
> > I think that effectively, the principles you suggest below
> > should allow to make a reasonable syntax, and no backward
> > incompatibility.
> > So basically:
> > * trailing } is mandatory
> > * inside a block of commands, { } and ; must be escaped
> > * a command block is limited to be on a single line
> > I guess multiple lines will be ok if each line is terminated
> > by a \.
> >
> > The above principles should also allow to extend if/while/...
> > to have 'one line' if/while/...
> >
> > I also think that 'one line if' will allow to do things such as:
> >
> > thread apply all if $ebp -$esp > 1000000 { echo big frame in this thread }
> >
> > (today, trying to use an 'if' in thread apply gives a quite strange
> > behaviour : the rest of the 'if' command is read during each execution
> > for each thread, which is not very user friendly :).
> >
> > I will work based on the above idea, and resubmit a draft patch
> > but that will for sure have to wait after FOSDEM, which
> > will already consume the night and week-end free time :).
> >
> > Thanks for the comments/help/nice suggestions
> >
>
>
> Hi.
> Sounds like a plan, but in an effort to make your time
> as productive as I can I would suggest first trying to come
> up with syntax that the community can accept.
Good idea, the plan is easier to write than the code.
>
> Among the questions we need to answer:
> - when/how to escape { } ;
> (what I gave was a strawman, I didn't dig deep
> into whether there are any gotchas)
> - what to do with { } in expressions,
> e.g., "if ({1,2,3})[2] == 3 { echo foo }"
> - ???
Here are some more details/answers:
Basic syntax
------------
Block of commands are single line, trailing } are mandatory.
These conditions are needed to allow to extend existing commands
(thread apply, if, while) in a (reasonably) backward compatible way.
The parsing of command blocks will be deferred to the commands
that accepts block of commands (or commands) as parameters.
Commands in a block are separated by ;.
; is optional in front of a { or a }, e.g.
{ echo 1\n { echo 2\n; echo 3\n ; } echo 4\n }
Escaping:
---------
{ } ; must be escaped inside a block of commands in order
to lose their 'block of command special character semantic'.
A single \ is good enough to escape, no need to have a \ for each
nesting level, e.g.
thread apply all { echo \{ at nesting 1 { echo \; at nesting 2 } }
No need to escape in expressions which are 'at nesting 0', e.g.
if ({1,2,3})[2] == 3 { echo foo }
The 'if' command parser will search for a trailing }
and will parse backward to search the matching opening {, counting
nesting levels. So, thanks to backward parsing, {}; do not need to
be escaped in expressions.
Only problem I see with this are expressions that are
terminated with a }.
Then the parser might confuse such an expression in an
'end terminated if' with a block of command of
a 'single line' if.
So, probably we will have to handle escaping the final } of such an
expression.
However, that will be a backward incompatibility.
I guess this incompatibility is unlikely, unless there is a language
where } are often terminating an expression ?
I do not see how to avoid this (unlikely?) backward incompatibility.
When 'if' is used inside a command block, then special characters
in the expression must be escaped e.g.
thread apply all { if (\{1,2,3\})[2] == 3 { echo three } }
Error handling
--------------
{/s ... }
means to continue executing the block if a command fails;
all the output of failing commands are silently ignored.
{/c .... }
also means to continue executing the block if a command fails,
but the error is reported.
Extension of existing commands
------------------------------
Commands that have a command as parameter or read other commands
will be extended to accept a block of commands (which must be
at the end of the line).
So:
thread apply all { .... }
if expression { ... } [else { ... } }
while expression { ... }
Is there something else to extend ?
Maybe single line define such as
define helloworld { echo hello; echo world\n }
?
Have a way to silence some commands using optional /s marker
------------------------------------------------------------
E.g.
thread apply all /s command
means that a thread is shown only if command has produced some output
Some new commands
-----------------
As discussed, new command
frame apply all
accepting also an optional /s
For completeness, I guess we better have:
inferior apply all ....
(and optional /s marker)
The idea is that all the above can be combined e.g.
inferior apply all /s { thread apply all /s { frame apply all /s if condition { echo look at this inferior/thread/frame }}}
(long single lines like that can be broken in separate lines using \ at the end of the line).
Note: I guess that the parser of inferior/thread/frame apply all
can be a 'forward parser', and so the first two { will be optional for the above.
Otherwise, inferior/thread/frame will also look for a terminating } and do a backward
parsing, then in the above example, all { } are needed to allow backward parsing.
I would have preferred to avoid the (unlikely I hope) incompatibility of }
terminated expressions, but I do not see a reasonable way to do that.
Comments/remarks/plan-killer-bugs/... welcome.
Philippe
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: block of commands
2016-01-11 21:40 Doug Evans
@ 2016-01-12 20:52 ` Philippe Waroquiers
0 siblings, 0 replies; 10+ messages in thread
From: Philippe Waroquiers @ 2016-01-12 20:52 UTC (permalink / raw)
To: Doug Evans; +Cc: gdb-patches
On Mon, 2016-01-11 at 21:40 +0000, Doug Evans wrote:
> Heya.
>
> One feature I've always wanted gdb's cli to have is the
> ability to catch errors and be able to continue.
> There's always a tension of whether to add things to the cli
> or just say "do it in python". gdb scripts are still useful
> and thus sometimes I think it's ok to extend the cli, such
> as in this case.
> Thus instead of /c in this patch I'd prefer something
> along the lines of "try ... end".
> ["end" is gdb's canonical "trailing }", and I'm all for
> Consistency Is Good here.]
> The actual details and semantics of "try" I would
> leave to a separate thread. For one, I'd have to think
> about it some more :-), but I would support someone
> wanting to go down that path.
>
> OTOH, trying to extend the cli's syntax with { and ; is not
> something I can support, at least not without more discussion.
> This example:
>
> thread apply all { set $prevsp = $sp; bt \{ p $ebp - $prevsp\; set
> $prevsp = $sp \} }
> => print the size of each frame
>
> suggests things could be better; having to escape braces within
> subcommands feels excessively error prone.
> gdb has one line per command, and changing that
> may make things too unwieldly.
The { (and have the terminating } optional) was chosen in order
to make the interactive typing of a block of command faster/easier.
I agree that this new syntax is very different of the
'usual gdb multi-lines terminated by end'.
However, multi-lines with end means the user cannot simply type e.g.
thread apply all { p i; p j}
Instead, the user must type:
define pij
p i
p j
end
thread apply all pij
And after, if the user wants to also print k,
then the pij sequence must be retyped (I do not know a way
to change/edit/complete a user defined command).
An alternative might be to make the terminating } mandatory.
Then I think we could get rid of the { and } escaping
So, the question is how much we want to make the block of commands
easy to use interactively ?
Maybe it is not that unusual to have 2 concepts
of 'list of commands' :
one command per line, with some 'begin/end' keywords
several commands per lines, separated by ;
(i.e. similar to what a shell is providing with if/fi and ;).
The ; separated list versus multi-lines is clearly the main
point of discussion.
Note that if ever the {}; idea survives, we have to take care
about parsing of existing scripts that would do e.g.
echo { is an open bracket
Not too sure it will be easy to make the {}; idea fully backward
compatible :(
> There are still
> macros so one could still do "thread apply all print-frame-size",
> and put the guts of frame size computation in a macro named
> print-frame-size.
True, but that makes the interactive use and editing
not so easy (cfr above).
>
> Also, I'd prefer to have generally composable commands
> over extending individual commands.
> E.g., one common thing to do with "thread apply" is backtrace,
> as in "thread apply all bt". I like that much better than
> if we had extended backtrace to work on each thread.
>
> Following "Consistency Is Good", and given that we have
> "thread apply", for your "bt {/c p i" case how about
> something like:
>
> frame apply all foo
Yes, that looks better than adding an optional command to
backtrace.
Philippe
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: block of commands
@ 2016-01-11 21:40 Doug Evans
2016-01-12 20:52 ` Philippe Waroquiers
0 siblings, 1 reply; 10+ messages in thread
From: Doug Evans @ 2016-01-11 21:40 UTC (permalink / raw)
To: Philippe Waroquiers; +Cc: gdb-patches
Philippe Waroquiers writes:
> 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.
Heya.
One feature I've always wanted gdb's cli to have is the
ability to catch errors and be able to continue.
There's always a tension of whether to add things to the cli
or just say "do it in python". gdb scripts are still useful
and thus sometimes I think it's ok to extend the cli, such
as in this case.
Thus instead of /c in this patch I'd prefer something
along the lines of "try ... end".
["end" is gdb's canonical "trailing }", and I'm all for
Consistency Is Good here.]
The actual details and semantics of "try" I would
leave to a separate thread. For one, I'd have to think
about it some more :-), but I would support someone
wanting to go down that path.
OTOH, trying to extend the cli's syntax with { and ; is not
something I can support, at least not without more discussion.
This example:
thread apply all { set $prevsp = $sp; bt \{ p $ebp - $prevsp\; set
$prevsp = $sp \} }
=> print the size of each frame
suggests things could be better; having to escape braces within
subcommands feels excessively error prone.
gdb has one line per command, and changing that
may make things too unwieldly. There are still
macros so one could still do "thread apply all print-frame-size",
and put the guts of frame size computation in a macro named
print-frame-size.
Also, I'd prefer to have generally composable commands
over extending individual commands.
E.g., one common thing to do with "thread apply" is backtrace,
as in "thread apply all bt". I like that much better than
if we had extended backtrace to work on each thread.
Following "Consistency Is Good", and given that we have
"thread apply", for your "bt {/c p i" case how about
something like:
frame apply all foo
?
> > 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."), _("\
> >
> >
> >
> >
>
>
--
/dje
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: block of commands
2015-11-21 18:41 Philippe Waroquiers
2015-12-03 22:28 ` Philippe Waroquiers
@ 2016-01-04 19:28 ` Philippe Waroquiers
1 sibling, 0 replies; 10+ messages in thread
From: Philippe Waroquiers @ 2016-01-04 19:28 UTC (permalink / raw)
To: gdb-patches
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."), _("\
>
>
>
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: block of commands
2015-12-03 22:28 ` Philippe Waroquiers
@ 2015-12-21 13:35 ` Philippe Waroquiers
0 siblings, 0 replies; 10+ messages in thread
From: Philippe Waroquiers @ 2015-12-21 13:35 UTC (permalink / raw)
To: gdb-patches
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."), _("\
> >
> >
> >
> >
> >
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: block of commands
2015-11-21 18:41 Philippe Waroquiers
@ 2015-12-03 22:28 ` Philippe Waroquiers
2015-12-21 13:35 ` Philippe Waroquiers
2016-01-04 19:28 ` Philippe Waroquiers
1 sibling, 1 reply; 10+ messages in thread
From: Philippe Waroquiers @ 2015-12-03 22:28 UTC (permalink / raw)
To: gdb-patches
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."), _("\
>
>
>
>
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* RFC: block of commands
@ 2015-11-21 18:41 Philippe Waroquiers
2015-12-03 22:28 ` Philippe Waroquiers
2016-01-04 19:28 ` Philippe Waroquiers
0 siblings, 2 replies; 10+ messages in thread
From: Philippe Waroquiers @ 2015-11-21 18:41 UTC (permalink / raw)
To: gdb-patches
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."), _("\
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2016-01-24 22:23 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-01-21 1:29 RFC: block of commands Doug Evans
2016-01-22 21:47 ` Philippe Waroquiers
2016-01-24 20:03 ` Doug Evans
2016-01-24 22:23 ` Philippe Waroquiers
-- strict thread matches above, loose matches on Subject: below --
2016-01-11 21:40 Doug Evans
2016-01-12 20:52 ` Philippe Waroquiers
2015-11-21 18:41 Philippe Waroquiers
2015-12-03 22:28 ` Philippe Waroquiers
2015-12-21 13:35 ` Philippe Waroquiers
2016-01-04 19:28 ` Philippe Waroquiers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox