gdb: split up construct_inferior_arguments

The function construct_inferior_arguments (gdbsupport/common-inferior.cc)
currently escapes all special shell characters.  After this commit
there will be two "levels" of quoting:

  1. The current "full" quoting, where all posix shell special
  characters are quoted, and

  2. a new "reduced" quoting, where only the characters that GDB sees
  as special (quotes and whitespace) are quoted.

After this, almost all construct_inferior_arguments calls will use the
"full" quoting, which is the current quoting.  The "reduced" quoting
will be used in this commit to restore the behaviour that was lost in
the previous commit (more details below).

In the future, the reduced quoting will be useful for some additional
inferior argument that I have planned.  I already posted my full
inferior argument work here:

  https://inbox.sourceware.org/gdb-patches/cover.1730731085.git.aburgess@redhat.com

But that series is pretty long, and wasn't getting reviewed, so I'm
posted the series in parts now.

Before the previous commit, GDB behaved like this:

  $ gdb -eiex 'set startup-with-shell off' --args /tmp/exec '$FOO'
  (gdb) show args
  Argument list to give program being debugged when it is started is "$FOO".

Notice that with 'startup-with-shell' off, the argument was left as
just '$FOO'.  But after the previous commit, this changed to:

  $ gdb -eiex 'set startup-with-shell off' --args /tmp/exec '$FOO'
  (gdb) show args
  Argument list to give program being debugged when it is started is "\$FOO".

Now the '$' is escaped with a backslash.  This commit restores the
original behaviour, as this is (currently) the only way to unquoted
shell special characters into arguments from the GDB command line.
The series that I listed above includes a new command line option for
GDB which provides a better approach for controlling the quoting of
special shell characters, but that work requires these patches to be
merged first.

I've split out the core of construct_inferior_arguments into the new
function escape_characters, which takes a set of characters to escape.
Then the two functions escape_shell_characters and
escape_gdb_characters call escape_characters with the appropriate
character sets.

Finally, construct_inferior_arguments, now takes a boolean which
indicates if we should perform full shell escaping, or just perform
the reduced escaping.

I've updated all uses of construct_inferior_arguments to pass a
suitable value to indicate what escaping to perform (mostly just
'true', but one case in main.c is different), also I've updated
inferior::set_args to take the same boolean flag, and pass it through
to construct_inferior_arguments.

Tested-By: Guinevere Larsen <guinevere@redhat.com>
This commit is contained in:
Andrew Burgess
2024-11-02 17:35:14 +00:00
parent 710f7df7da
commit 512ca2fca4
8 changed files with 127 additions and 61 deletions

View File

@@ -1174,7 +1174,7 @@ core_target_open (const char *arg, int from_tty)
for (const gdb::unique_xmalloc_ptr<char> &a : ctx.args ())
argv.push_back (a.get ());
gdb::array_view<char * const> view (argv.data (), argv.size ());
current_inferior ()->set_args (view);
current_inferior ()->set_args (view, true);
/* And now copy the environment. */
current_inferior ()->environment = ctx.environment ();

View File

@@ -167,9 +167,10 @@ inferior::tty ()
/* See inferior.h. */
void
inferior::set_args (gdb::array_view<char * const> args)
inferior::set_args (gdb::array_view<char * const> args,
bool escape_shell_char)
{
set_args (construct_inferior_arguments (args));
set_args (construct_inferior_arguments (args, escape_shell_char));
}
void

View File

@@ -522,8 +522,11 @@ public:
m_args = std::move (args);
};
/* Set the argument string from some strings. */
void set_args (gdb::array_view<char * const> args);
/* Set the argument string from some strings in ARGS. When
ESCAPE_SHELL_CHAR is true all special shell characters in ARGS are
escaped, When false only the characters that GDB sees as special will
be escaped. See construct_inferior_arguments for more details. */
void set_args (gdb::array_view<char * const> args, bool escape_shell_char);
/* Get the argument string to use when running this inferior.

View File

@@ -1078,7 +1078,8 @@ captured_main_1 (struct captured_main_args *context)
execarg = argv[optind];
++optind;
current_inferior ()->set_args
(gdb::array_view<char * const> (&argv[optind], argc - optind));
(gdb::array_view<char * const> (&argv[optind], argc - optind),
startup_with_shell);
}
else
{

View File

@@ -929,7 +929,7 @@ infpy_set_args (PyObject *self, PyObject *value, void *closure)
for (const auto &arg : args)
argvec.push_back (arg.get ());
gdb::array_view<char * const> view (argvec.data (), argvec.size ());
inf->inferior->set_args (view);
inf->inferior->set_args (view, true);
}
else
{

View File

@@ -3465,7 +3465,7 @@ handle_v_run (char *own_buf)
else
program_path.set (new_program_name.get ());
program_args = construct_inferior_arguments (new_argv.get ());
program_args = construct_inferior_arguments (new_argv.get (), true);
try
{
@@ -4355,7 +4355,7 @@ captured_main (int argc, char *argv[])
int n = argc - (next_arg - argv);
program_args
= construct_inferior_arguments ({&next_arg[1], &next_arg[n]});
= construct_inferior_arguments ({&next_arg[1], &next_arg[n]}, true);
/* Wait till we are at first instruction in program. */
target_create_inferior (program_path.get (), program_args);

View File

@@ -24,74 +24,131 @@
bool startup_with_shell = true;
/* See common-inferior.h. */
/* Escape characters in ARG and return an updated string. The string
SPECIAL contains the set of characters that must be escaped. SPECIAL
must not be nullptr, and it is assumed that SPECIAL contains the newline
'\n' character. It is assumed that ARG is not nullptr, but ARG can
be the empty string. */
std::string
construct_inferior_arguments (gdb::array_view<char * const> argv)
static std::string
escape_characters (const char *arg, const char *special)
{
gdb_assert (special != nullptr);
gdb_assert (arg != nullptr);
std::string result;
#ifdef __MINGW32__
static const char quote = '"';
#else
static const char quote = '\'';
#endif
/* Need to handle empty arguments specially. */
if (arg[0] == '\0')
{
result += quote;
result += quote;
}
/* The special character handling code here assumes that if SPECIAL is
not nullptr, then SPECIAL will contain '\n'. This is true for all our
current usages, but if this ever changes in the future the following
might need reworking. */
else
{
#ifdef __MINGW32__
bool quoted = false;
if (strpbrk (argv[i], special))
{
quoted = true;
result += quote;
}
#endif
for (const char *cp = arg; *cp; ++cp)
{
if (*cp == '\n')
{
/* A newline cannot be quoted with a backslash (it just
disappears), only by putting it inside quotes. */
result += quote;
result += '\n';
result += quote;
}
else
{
#ifdef __MINGW32__
if (*cp == quote)
#else
if (strchr (special, *cp) != nullptr)
#endif
result += '\\';
result += *cp;
}
}
#ifdef __MINGW32__
if (quoted)
result += quote;
#endif
}
return result;
}
/* Return a version of ARG that has special shell characters escaped. */
static std::string
escape_shell_characters (const char *arg)
{
#ifdef __MINGW32__
/* This holds all the characters considered special to the
Windows shells. */
static const char special[] = "\"!&*|[]{}<>?`~^=;, \t\n";
static const char quote = '"';
#else
/* This holds all the characters considered special to the
typical Unix shells. We include `^' because the SunOS
/bin/sh treats it as a synonym for `|'. */
static const char special[] = "\"!#$&*()\\|[]{}<>?'`~^; \t\n";
static const char quote = '\'';
#endif
for (int i = 0; i < argv.size (); ++i)
{
if (i > 0)
result += ' ';
/* Need to handle empty arguments specially. */
if (argv[i][0] == '\0')
{
result += quote;
result += quote;
}
else
{
#ifdef __MINGW32__
bool quoted = false;
return escape_characters (arg, special);
}
if (strpbrk (argv[i], special))
{
quoted = true;
result += quote;
}
#endif
for (char *cp = argv[i]; *cp != '\0'; ++cp)
{
if (*cp == '\n')
{
/* A newline cannot be quoted with a backslash (it
just disappears), only by putting it inside
quotes. */
result += quote;
result += '\n';
result += quote;
}
else
{
/* Return a version of ARG that has quote characters and white space
characters escaped. These are the characters that GDB sees as special
when splitting a string into separate arguments. */
static std::string
escape_gdb_characters (const char * arg)
{
#ifdef __MINGW32__
if (*cp == quote)
static const char special[] = "\" \t\n";
#else
if (strchr (special, *cp) != NULL)
static const char special[] = "\"' \t\n";
#endif
result += '\\';
result += *cp;
}
}
#ifdef __MINGW32__
if (quoted)
result += quote;
#endif
}
return escape_characters (arg, special);
}
/* See common-inferior.h. */
std::string
construct_inferior_arguments (gdb::array_view<char * const> argv,
bool escape_shell_char)
{
/* Select the desired escape function. */
const auto escape_func = (escape_shell_char
? escape_shell_characters
: escape_gdb_characters);
std::string result;
for (const char *a : argv)
{
if (!result.empty ())
result += " ";
result += escape_func (a);
}
return result;

View File

@@ -52,9 +52,13 @@ extern const std::string &get_inferior_cwd ();
the target is started up with a shell. */
extern bool startup_with_shell;
/* Compute command-line string given argument vector. This does the
same shell processing as fork_inferior. */
/* Combine elements of ARGV into a single string, placing a single
whitespace character between each element. When ESCAPE_SHELL_CHAR is
true then any special shell characters in elemets of ARGV will be
escaped. When ESCAPE_SHELL_CHAR is false only the characters that GDB
sees as special (quotes and whitespace) are escaped. */
extern std::string
construct_inferior_arguments (gdb::array_view<char * const>);
construct_inferior_arguments (gdb::array_view<char * const> argv,
bool escape_shell_char);
#endif /* GDBSUPPORT_COMMON_INFERIOR_H */