mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-01-06 12:09:26 +08:00
Add new "$_shell(CMD)" internal function
For testing a following patch, I wanted a way to send a SIGINT to GDB from a breakpoint condition. And I didn't want to do it from a Python breakpoint or Python function, as I wanted to exercise non-Python code paths. So I thought I'd add a new $_shell internal function, that runs a command under the shell, and returns the exit code. With this, I could write: (gdb) b foo if $_shell("kill -SIGINT $gdb_pid") != 0 || <other condition> I think this is generally useful, hence I'm proposing it here. Here's the new function in action: (gdb) p $_shell("true") $1 = 0 (gdb) p $_shell("false") $2 = 1 (gdb) p $_shell("echo hello") hello $3 = 0 (gdb) p $_shell("foobar") bash: line 1: foobar: command not found $4 = 127 (gdb) help function _shell $_shell - execute a shell command and returns the result. Usage: $_shell (command) Returns the command's exit code: zero on success, non-zero otherwise. (gdb) NEWS and manual changes included. Approved-By: Andrew Burgess <aburgess@redhat.com> Approved-By: Tom Tromey <tom@tromey.com> Approved-By: Eli Zaretskii <eliz@gnu.org> Change-Id: I7e36d451ee6b428cbf41fded415ae2d6b4efaa4e
This commit is contained in:
parent
751495be92
commit
91265a7d7c
10
gdb/NEWS
10
gdb/NEWS
@ -68,6 +68,16 @@ maintenance info frame-unwinders
|
||||
List the frame unwinders currently in effect, starting with the highest
|
||||
priority.
|
||||
|
||||
* New convenience function "$_shell", to execute a shell command and
|
||||
return the result. This lets you run shell commands in expressions.
|
||||
Some examples:
|
||||
|
||||
(gdb) p $_shell("true")
|
||||
$1 = 0
|
||||
(gdb) p $_shell("false")
|
||||
$2 = 1
|
||||
(gdb) break func if $_shell("some command") == 0
|
||||
|
||||
* MI changes
|
||||
|
||||
** mi now reports 'no-history' as a stop reason when hitting the end of the
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "gdbsupport/filestuff.h"
|
||||
#include "location.h"
|
||||
#include "block.h"
|
||||
#include "valprint.h"
|
||||
|
||||
#include "ui-out.h"
|
||||
#include "interps.h"
|
||||
@ -873,6 +874,9 @@ exit_status_set_internal_vars (int exit_status)
|
||||
|
||||
clear_internalvar (var_code);
|
||||
clear_internalvar (var_signal);
|
||||
|
||||
/* Keep the logic here in sync with shell_internal_fn. */
|
||||
|
||||
if (WIFEXITED (exit_status))
|
||||
set_internalvar_integer (var_code, WEXITSTATUS (exit_status));
|
||||
#ifdef __MINGW32__
|
||||
@ -893,8 +897,11 @@ exit_status_set_internal_vars (int exit_status)
|
||||
warning (_("unexpected shell command exit status %d"), exit_status);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_escape (const char *arg, int from_tty)
|
||||
/* Run ARG under the shell, and return the exit status. If ARG is
|
||||
NULL, run an interactive shell. */
|
||||
|
||||
static int
|
||||
run_under_shell (const char *arg, int from_tty)
|
||||
{
|
||||
#if defined(CANT_FORK) || \
|
||||
(!defined(HAVE_WORKING_VFORK) && !defined(HAVE_WORKING_FORK))
|
||||
@ -915,7 +922,7 @@ shell_escape (const char *arg, int from_tty)
|
||||
the shell command we just ran changed it. */
|
||||
chdir (current_directory);
|
||||
#endif
|
||||
exit_status_set_internal_vars (rc);
|
||||
return rc;
|
||||
#else /* Can fork. */
|
||||
int status, pid;
|
||||
|
||||
@ -942,10 +949,21 @@ shell_escape (const char *arg, int from_tty)
|
||||
waitpid (pid, &status, 0);
|
||||
else
|
||||
error (_("Fork failed"));
|
||||
exit_status_set_internal_vars (status);
|
||||
return status;
|
||||
#endif /* Can fork. */
|
||||
}
|
||||
|
||||
/* Escape out to the shell to run ARG. If ARG is NULL, launch and
|
||||
interactive shell. Sets $_shell_exitcode and $_shell_exitsignal
|
||||
convenience variables based on the exits status. */
|
||||
|
||||
static void
|
||||
shell_escape (const char *arg, int from_tty)
|
||||
{
|
||||
int status = run_under_shell (arg, from_tty);
|
||||
exit_status_set_internal_vars (status);
|
||||
}
|
||||
|
||||
/* Implementation of the "shell" command. */
|
||||
|
||||
static void
|
||||
@ -2417,6 +2435,63 @@ gdb_maint_setting_str_internal_fn (struct gdbarch *gdbarch,
|
||||
return str_value_from_setting (*show_cmd->var, gdbarch);
|
||||
}
|
||||
|
||||
/* Implementation of the convenience function $_shell. */
|
||||
|
||||
static struct value *
|
||||
shell_internal_fn (struct gdbarch *gdbarch,
|
||||
const struct language_defn *language,
|
||||
void *cookie, int argc, struct value **argv)
|
||||
{
|
||||
if (argc != 1)
|
||||
error (_("You must provide one argument for $_shell."));
|
||||
|
||||
value *val = argv[0];
|
||||
struct type *type = check_typedef (val->type ());
|
||||
|
||||
if (!language->is_string_type_p (type))
|
||||
error (_("Argument must be a string."));
|
||||
|
||||
value_print_options opts;
|
||||
get_no_prettyformat_print_options (&opts);
|
||||
|
||||
string_file stream;
|
||||
value_print (val, &stream, &opts);
|
||||
|
||||
/* We should always have two quote chars, which we'll strip. */
|
||||
gdb_assert (stream.size () >= 2);
|
||||
|
||||
/* Now strip them. We don't need the original string, so it's
|
||||
cheaper to do it in place, avoiding a string allocation. */
|
||||
std::string str = stream.release ();
|
||||
str[str.size () - 1] = 0;
|
||||
const char *command = str.c_str () + 1;
|
||||
|
||||
int exit_status = run_under_shell (command, 0);
|
||||
|
||||
struct type *int_type = builtin_type (gdbarch)->builtin_int;
|
||||
|
||||
/* Keep the logic here in sync with
|
||||
exit_status_set_internal_vars. */
|
||||
|
||||
if (WIFEXITED (exit_status))
|
||||
return value_from_longest (int_type, WEXITSTATUS (exit_status));
|
||||
#ifdef __MINGW32__
|
||||
else if (WIFSIGNALED (exit_status) && WTERMSIG (exit_status) == -1)
|
||||
{
|
||||
/* See exit_status_set_internal_vars. */
|
||||
return value_from_longest (int_type, exit_status);
|
||||
}
|
||||
#endif
|
||||
else if (WIFSIGNALED (exit_status))
|
||||
{
|
||||
/* (0x80 | SIGNO) is what most (all?) POSIX-like shells set as
|
||||
exit code on fatal signal termination. */
|
||||
return value_from_longest (int_type, 0x80 | WTERMSIG (exit_status));
|
||||
}
|
||||
else
|
||||
return value::allocate_optimized_out (int_type);
|
||||
}
|
||||
|
||||
void _initialize_cli_cmds ();
|
||||
void
|
||||
_initialize_cli_cmds ()
|
||||
@ -2606,6 +2681,19 @@ Some integer settings accept an unlimited value, returned\n\
|
||||
as 0 or -1 depending on the setting."),
|
||||
gdb_maint_setting_internal_fn, NULL);
|
||||
|
||||
add_internal_function ("_shell", _("\
|
||||
$_shell - execute a shell command and return the result.\n\
|
||||
\n\
|
||||
Usage: $_shell (COMMAND)\n\
|
||||
\n\
|
||||
Arguments:\n\
|
||||
\n\
|
||||
COMMAND: The command to execute. Must be a string.\n\
|
||||
\n\
|
||||
Returns:\n\
|
||||
The command's exit code: zero on success, non-zero otherwise."),
|
||||
shell_internal_fn, NULL);
|
||||
|
||||
add_cmd ("commands", no_set_class, show_commands, _("\
|
||||
Show the history of commands you typed.\n\
|
||||
You can supply a command number to start with, or a `+' to start after\n\
|
||||
|
@ -1621,7 +1621,7 @@ just use the @code{shell} command.
|
||||
@cindex shell escape
|
||||
@item shell @var{command-string}
|
||||
@itemx !@var{command-string}
|
||||
Invoke a standard shell to execute @var{command-string}.
|
||||
Invoke a shell to execute @var{command-string}.
|
||||
Note that no space is needed between @code{!} and @var{command-string}.
|
||||
On GNU and Unix systems, the environment variable @env{SHELL}, if it
|
||||
exists, determines which shell to run. Otherwise @value{GDBN} uses
|
||||
@ -1629,6 +1629,10 @@ the default shell (@file{/bin/sh} on GNU and Unix systems,
|
||||
@file{cmd.exe} on MS-Windows, @file{COMMAND.COM} on MS-DOS, etc.).
|
||||
@end table
|
||||
|
||||
You may also invoke shell commands from expressions, using the
|
||||
@code{$_shell} convenience function. @xref{$_shell convenience
|
||||
function}.
|
||||
|
||||
The utility @code{make} is often needed in development environments.
|
||||
You do not have to use the @code{shell} command for this purpose in
|
||||
@value{GDBN}:
|
||||
@ -12977,6 +12981,60 @@ Like the @code{$_gdb_setting_str} function, but works with
|
||||
Like the @code{$_gdb_setting} function, but works with
|
||||
@code{maintenance set} variables.
|
||||
|
||||
@anchor{$_shell convenience function}
|
||||
@findex $_shell@r{, convenience function}
|
||||
@item $_shell (@var{command-string})
|
||||
|
||||
Invoke a shell to execute @var{command-string}. @var{command-string}
|
||||
must be a string. The shell runs on the host machine, the machine
|
||||
@value{GDBN} is running on. Returns the command's exit status. On
|
||||
Unix systems, a command which exits with a zero exit status has
|
||||
succeeded, and non-zero exit status indicates failure. When a command
|
||||
terminates on a fatal signal whose number is @var{N}, @value{GDBN}
|
||||
uses the value 128+@var{N} as the exit status, as is standard in Unix
|
||||
shells. Note that @var{N} is a host signal number, not a target
|
||||
signal number. If you're native debugging, they will be the same, but
|
||||
if cross debugging, the host vs target signal numbers may be
|
||||
completely unrelated. Please consult your host operating system's
|
||||
documentation for the mapping between host signal numbers and signal
|
||||
names. The shell to run is determined in the same way as for the
|
||||
@code{shell} command. @xref{Shell Commands, ,Shell Commands}.
|
||||
|
||||
@smallexample
|
||||
(@value{GDBP}) print $_shell("true")
|
||||
$1 = 0
|
||||
(@value{GDBP}) print $_shell("false")
|
||||
$2 = 1
|
||||
(@value{GDBP}) p $_shell("echo hello")
|
||||
hello
|
||||
$3 = 0
|
||||
(@value{GDBP}) p $_shell("foobar")
|
||||
bash: line 1: foobar: command not found
|
||||
$4 = 127
|
||||
@end smallexample
|
||||
|
||||
This may also be useful in breakpoint conditions. For example:
|
||||
|
||||
@smallexample
|
||||
(@value{GDBP}) break function if $_shell("some command") == 0
|
||||
@end smallexample
|
||||
|
||||
In this scenario, you'll want to make sure that the shell command you
|
||||
run in the breakpoint condition takes the least amount of time
|
||||
possible. For example, avoid running a command that may block
|
||||
indefinitely, or that sleeps for a while before exiting. Prefer a
|
||||
command or script which analyzes some state and exits immediately.
|
||||
This is important because the debugged program stops for the
|
||||
breakpoint every time, and then @value{GDBN} evaluates the breakpoint
|
||||
condition. If the condition is false, the program is re-resumed
|
||||
transparently, without informing you of the stop. A quick shell
|
||||
command thus avoids significantly slowing down the debugged program
|
||||
unnecessarily.
|
||||
|
||||
Note: unlike the @code{shell} command, the @code{$_shell} convenience
|
||||
function does not affect the @code{$_shell_exitcode} and
|
||||
@code{$_shell_exitsignal} convenience variables.
|
||||
|
||||
@end table
|
||||
|
||||
The following functions require @value{GDBN} to be configured with
|
||||
|
@ -606,6 +606,7 @@ set show_conv_list \
|
||||
{$_cimag = <internal function _cimag>} \
|
||||
{$_creal = <internal function _creal>} \
|
||||
{$_isvoid = <internal function _isvoid>} \
|
||||
{$_shell = <internal function _shell>} \
|
||||
{$_gdb_maint_setting_str = <internal function _gdb_maint_setting_str>} \
|
||||
{$_gdb_maint_setting = <internal function _gdb_maint_setting>} \
|
||||
{$_gdb_setting_str = <internal function _gdb_setting_str>} \
|
||||
|
@ -41,6 +41,42 @@ if { ! [ishost *-*-mingw*] } {
|
||||
gdb_test "p \$_shell_exitsignal" " = 2" "shell interrupt exitsignal"
|
||||
}
|
||||
|
||||
# Test the $_shell convenience function.
|
||||
|
||||
with_test_prefix "\$_shell convenience function" {
|
||||
# Simple commands, check the result code.
|
||||
gdb_test "p \$_shell(\"true\")" " = 0"
|
||||
gdb_test "p \$_shell(\"false\")" " = 1"
|
||||
|
||||
# Test command with arguments.
|
||||
gdb_test "p \$_shell(\"echo foo\")" "foo\r\n\\$${decimal} = 0"
|
||||
|
||||
# Check the type of the result.
|
||||
gdb_test "ptype \$_shell(\"true\")" "type = int"
|
||||
|
||||
# Test passing a non-literal string as command name.
|
||||
gdb_test "p \$cmd = \"echo bar\"" " = \"echo bar\""
|
||||
gdb_test "p \$_shell(\$cmd)" "bar\r\n\\$${decimal} = 0"
|
||||
|
||||
# Test executing a non-existing command. The result is
|
||||
# shell-dependent, but most (all?) POSIX-like shells return 127 in
|
||||
# this case.
|
||||
gdb_test "p \$_shell(\"non-existing-command-foo-bar-qux\")" " = 127"
|
||||
|
||||
gdb_test "p \$_shell" \
|
||||
" = <internal function _shell>"
|
||||
gdb_test "ptype \$_shell" \
|
||||
"type = <internal function>"
|
||||
|
||||
# Test error scenarios.
|
||||
gdb_test "p \$_shell()" \
|
||||
"You must provide one argument for \\\$_shell\\\."
|
||||
gdb_test "p \$_shell(\"a\", \"b\")" \
|
||||
"You must provide one argument for \\\$_shell\\\."
|
||||
gdb_test "p \$_shell(1)" \
|
||||
"Argument must be a string\\\."
|
||||
}
|
||||
|
||||
# Define the user command "foo", used to test "pipe" command.
|
||||
gdb_test_multiple "define foo" "define foo" {
|
||||
-re "End with" {
|
||||
|
Loading…
Reference in New Issue
Block a user