]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb/gdbserver: add a '--no-escape-args' command line option
authorMichael Weghorn <m.weghorn@posteo.de>
Fri, 22 Oct 2021 07:19:29 +0000 (07:19 +0000)
committerAndrew Burgess <aburgess@redhat.com>
Fri, 12 Sep 2025 08:50:03 +0000 (09:50 +0100)
This introduces a new '--no-escape-args' option for gdb and gdbserver.

I (Andrew Burgess) have based this patch from work done in this
series:

  https://inbox.sourceware.org/gdb-patches/20211022071933.3478427-1-m.weghorn@posteo.de/

I have changed things slightly from the original series.  I think this
work is close enough that I've left the original author (Michael) in
place and added myself as co-author.  Any bugs introduced by my
modifications to the original patch should be considered mine.  I've
also added documentation and tests which were missing from the
originally proposed patch.

When the startup-with-shell option is enabled, arguments passed
directly as 'gdb --args <args>' or 'gdbserver <args>', are by default
escaped so that they are passed to the inferior as passed on the
command line, no globbing or variable substitution happens within the
shell GDB uses to start the inferior.

For gdbserver, this is the case since commit:

  commit bea571ebd78ee29cb94adf648fbcda1e109e1be6
  Date:   Mon May 25 11:39:43 2020 -0400

      Use construct_inferior_arguments which handles special chars

Only arguments set via 'set args <args>', 'run <args>', or through the
Python API are not escaped in standard upstream GDB right now.

For the 'gdb --args' case, directly setting unescaped args on gdb
invocation is possible e.g. by using the "--eval-command='set args
<args>'", while this possibility does not exist for gdbserver.

This commit adds a new '--no-escape-args' command line option for GDB
and gdbserver.  This option is used with GDB as a replacement for the
current '--args' option, and for gdbserver this new option is a flag
which changes how gdbserver handles inferior arguments on the command
line.  When '--no-escape-args' is used inferior arguments passed on
the command line will not have escaping added by GDB or gdbserver.

For gdbserver, using this new option allows having the behaviour from
before commit bea571ebd78ee29cb94adf648fbcda1e109e1be6, while keeping
the default behaviour unified between GDB and GDBserver.

For GDB the --no-escape-args option can be used as a replacement for
--args, like this:

  shell> gdb --no-escape-args my-program arg1 arg2 arg3

While for gdbserver, the --no-escape-args option is a flag, which can
be used like:

  shell> gdbserver --no-escape-args --once localhost:54321 \
             my-program arg1 arg2 arg3

Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28392

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Tested-By: Guinevere Larsen <guinevere@redhat.com>
gdb/NEWS
gdb/doc/gdb.texinfo
gdb/main.c
gdb/testsuite/gdb.base/args.exp
gdb/testsuite/gdb.server/inferior-args.c [new file with mode: 0644]
gdb/testsuite/gdb.server/inferior-args.exp [new file with mode: 0644]
gdbserver/server.cc

index 1a9cd9df8971e17c295301b6cac32b663f37f93e..1daddd059eb80f37d5c159dad60f752c3fb25168 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -6,6 +6,14 @@
 * Support for .gdb_index sections with version less than 7 has been
   removed.
 
+* GDB now accepts --no-escape-args as an alternative to --args on the
+  command line.  GDB will not escape special shell characters within
+  arguments after --no-escape-args.
+
+* gdbserver now accepts --no-escape-args as a command line flag.  When
+  this flag is used gdbserver will not escape special shell characters
+  within the inferior arguments.
+
 * New targets
 
 GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
index dd9498c3dc8c666ba7d766a7fd4770289cd12783..f8154212fa438553ceb50f7021f3c88398cff397 100644 (file)
@@ -898,14 +898,17 @@ debugger attached to a bare board, there may not be any notion of
 ``process'', and there is often no way to get a core dump.  @value{GDBN}
 will warn you if it is unable to attach or to read core dumps.
 
-You can optionally have @code{@value{GDBP}} pass any arguments after the
-executable file to the inferior using @code{--args}.  This option stops
-option processing.
+You can optionally have @code{@value{GDBP}} pass any arguments after
+the executable file to the inferior using @code{--args} or
+@code{--no-escape-args}.  These options stop option processing.
 @smallexample
 @value{GDBP} --args gcc -O2 -c foo.c
 @end smallexample
 This will cause @code{@value{GDBP}} to debug @code{gcc}, and to set
-@code{gcc}'s command-line arguments (@pxref{Arguments}) to @samp{-O2 -c foo.c}.
+@code{gcc}'s command-line arguments (@pxref{Arguments}) to @samp{-O2
+-c foo.c}.  For the differences between @code{--args} and
+@code{--no-escape-args}, see @ref{--args and --no-escape-args options,
+,@code{--args} and @code{--no-escape-args}}.
 
 You can run @code{@value{GDBP}} without printing the front material, which describes
 @value{GDBN}'s non-warranty, by specifying @code{--silent}
@@ -1240,12 +1243,67 @@ that control @value{GDBN}, and level 2 has been deprecated.
 The annotation mechanism has largely been superseded by @sc{gdb/mi}
 (@pxref{GDB/MI}).
 
+@anchor{--args and --no-escape-args options}
 @item --args
 @cindex @code{--args}
 Change interpretation of command line so that arguments following the
 executable file are passed as command line arguments to the inferior.
 This option stops option processing.
 
+Arguments supplied using @code{--args} will have backslashes applied
+to escape any special shell characters.  This ensures that when the
+inferior starts it is passed arguments exactly as @value{GDBN}
+receives them.
+
+For example, consider the following command run under a shell:
+
+@smallexample
+$ @value{GDBP} --args ls *.c
+@end smallexample
+
+@noindent
+In this case the shell will expand @kbd{*.c} at the time @value{GDBN}
+is invoked, not at the time that the inferior is invoked.  As a
+result, if an additional @kbd{.c} file is created after @value{GDBN}
+is started, but before the inferior is started, then the inferior will
+not show the file in its output; the list of matching files was
+resolved at the time @value{GDBN} was started.
+
+If you quote the @kbd{*} character used on the @value{GDBN} command
+line argument then this will prevent the shell that starts
+@value{GDBN} from expanding the @kbd{*.c} pattern, however, this
+quoting will also be passed to the shell that @value{GDBN} invokes in
+order to start the inferior (@pxref{set startup-with-shell}), and this
+will prevent the @kbd{*.c} pattern being expanded at this point either:
+
+@smallexample
+$ @value{GDBP} --args ls '*.c'
+(@value{GDBP}) show args
+Argument list to give program being debugged when it is started is "\*.log".
+@end smallexample
+
+@noindent
+If this quoting behaviour does not meet your needs, then you could use
+@code{--no-escape-args} instead, which is described below.
+
+@item --no-escape-args
+@cindex @code{--no-escape-args}
+Change interpretation of command line so that arguments following the
+executable file are passed as command line arguments to the inferior.
+This option stops option processing.
+
+Unlike @code{--args}, arguments after the executable name will not
+have any escaping applied to them.  As a result, any special shell
+characters that are not expanded by the shell that invokes
+@value{GDBN} will be expanded by the shell that @value{GDBN} uses to
+start the inferior.
+
+@smallexample
+$ @value{GDBP} --no-escape-args ls '*.c'
+(@value{GDBP}) show args
+Argument list to give program being debugged when it is started is "*.log".
+@end smallexample
+
 @item -baud @var{bps}
 @itemx -b @var{bps}
 @cindex @code{--baud}
@@ -51236,9 +51294,10 @@ Note that targets that give their output via @value{GDBN}, as opposed to writing
 directly to @code{stdout}, will also be made silent.
 
 @item --args @var{prog} [@var{arglist}]
-Change interpretation of command line so that arguments following this
-option are passed as arguments to the inferior.  As an example, take
-the following command:
+@itemx --no-escape-args @var{prog} [@var{arglist}]
+Change interpretation of command line so that arguments following
+either of these options are passed as arguments to the inferior.  As
+an example, take the following command:
 
 @smallexample
 gdb ./a.out -q
@@ -51253,7 +51312,44 @@ gdb --args ./a.out -q
 @end smallexample
 
 @noindent
-starts @value{GDBN} with the introductory message, and passes the option to the inferior.
+starts @value{GDBN} with the introductory message, and passes the
+option @code{-q} to the inferior.
+
+The difference between @option{--args} and @option{--no-escape-args}
+is whether @value{GDBN} applies escapes to the arguments it sees:
+
+@smallexample
+gdb --args ./a.out *.c
+@end smallexample
+
+@noindent
+in this case the @code{*.c} is expanded by the shell that invokes
+@value{GDBN}, the list of matching files will be fixed in the inferior
+argument list.  If instead this is used:
+
+@smallexample
+gdb --args ./a.out '*.c'
+@end smallexample
+
+@noindent
+then the shell that invokes @value{GDBN} will not expand @code{*.c},
+instead @value{GDBN} will escape the @code{*} character, so when a.out
+is invoked it will be passed a literal @code{*.c}.  If instead this is
+used:
+
+@smallexample
+gdb --no-escape-args ./a.out '*.c'
+@end smallexample
+
+@noindent
+now @value{GDBN} will not escape the @code{*} character.  When the
+inferior is invoked the @code{*.c} will be expanded, and the inferior
+will be passed the list of files as present at the time the inferior
+is invoked.
+
+This change of behaviour can be important if the list of matching
+files could change between the time that @value{GDBN} is started, and
+the time the inferior is started.
 
 @item --pid=@var{pid}
 Attach @value{GDBN} to an already running program, with the PID @var{pid}.
@@ -51595,6 +51691,18 @@ additional connections are possible.  However, if you start @code{gdbserver}
 with the @option{--once} option, it will stop listening for any further
 connection attempts after connecting to the first @value{GDBN} session.
 
+@item --no-escape-args
+By default, inferior arguments passed on the @command{gdbserver}
+command line will have any special shell characters escaped by
+@command{gdbserver}.  This ensures that when @command{gdbserver}
+invokes the inferior, the arguments passed to the inferior are
+identical to the arguments passed to @command{gdbserver}.
+
+To disable this escaping, use @option{--no-escape-args}.  With this
+option special shell characters will not be escaped.  When
+@command{gdbserver} starts a new shell in order to invoke the
+inferior, this new shell will expand any special shell characters.
+
 @c --disable-packet is not documented for users.
 
 @c --disable-randomization and --no-disable-randomization are superseded by
index e1ef537c3572c0554e768a45b6eaa4767c57340e..04c33638bec13a33526676697a1a7910824ebe32 100644 (file)
@@ -617,9 +617,20 @@ captured_main_1 (struct captured_main_args *context)
   char **argv = context->argv;
 
   static int quiet = 0;
-  static int set_args = 0;
   static int inhibit_home_gdbinit = 0;
 
+  /* Has the user passed inferior arguments on the command line.  */
+  enum {
+    /* No arguments passed.  */
+    NO_ARGS,
+
+    /* Arguments passed with --args.  */
+    SET_ESC_ARGS,
+
+    /* Arguments passed with --no-escape-args.  */
+    SET_NO_ESC_ARGS
+  } set_args = NO_ARGS;
+
   /* Pointers to various arguments from command line.  */
   char *symarg = NULL;
   char *execarg = NULL;
@@ -769,7 +780,9 @@ captured_main_1 (struct captured_main_args *context)
       OPT_EIX,
       OPT_EIEX,
       OPT_READNOW,
-      OPT_READNEVER
+      OPT_READNEVER,
+      OPT_SET_ESC_ARGS,
+      OPT_SET_NO_ESC_ARGS,
     };
     /* This struct requires int* in the struct, but write_files is a bool.
        So use this temporary int that we write back after argument parsing.  */
@@ -842,7 +855,8 @@ captured_main_1 (struct captured_main_args *context)
       {"windows", no_argument, NULL, OPT_WINDOWS},
       {"statistics", no_argument, 0, OPT_STATISTICS},
       {"write", no_argument, &write_files_1, 1},
-      {"args", no_argument, &set_args, 1},
+      {"args", no_argument, nullptr, OPT_SET_ESC_ARGS},
+      {"no-escape-args", no_argument, nullptr, OPT_SET_NO_ESC_ARGS},
       {"l", required_argument, 0, 'l'},
       {"return-child-result", no_argument, &return_child_result, 1},
       {0, no_argument, 0, 0}
@@ -854,7 +868,7 @@ captured_main_1 (struct captured_main_args *context)
 
        c = getopt_long_only (argc, argv, "",
                              long_options, &option_index);
-       if (c == EOF || set_args)
+       if (c == EOF || set_args != NO_ARGS)
          break;
 
        /* Long option that takes an argument.  */
@@ -935,6 +949,12 @@ captured_main_1 (struct captured_main_args *context)
          case OPT_EIEX:
            cmdarg_vec.emplace_back (CMDARG_EARLYINIT_COMMAND, optarg);
            break;
+         case OPT_SET_ESC_ARGS:
+           set_args = SET_ESC_ARGS;
+           break;
+         case OPT_SET_NO_ESC_ARGS:
+           set_args = SET_NO_ESC_ARGS;
+           break;
          case 'B':
            batch_flag = batch_silent = 1;
            gdb_stdout = new null_file ();
@@ -1068,7 +1088,7 @@ captured_main_1 (struct captured_main_args *context)
 
   /* Now that gdb_init has created the initial inferior, we're in
      position to set args for that inferior.  */
-  if (set_args)
+  if (set_args != NO_ARGS)
     {
       /* The remaining options are the command-line options for the
         inferior.  The first one is the sym/exec file, and the rest
@@ -1080,10 +1100,9 @@ captured_main_1 (struct captured_main_args *context)
       symarg = argv[optind];
       execarg = argv[optind];
       ++optind;
-      current_inferior ()->set_args
-       (gdb::array_view<char * const> (&argv[optind], argc - optind),
-        startup_with_shell);
-    }
+      gdb::array_view<char * const> arg_view (&argv[optind], argc - optind);
+      current_inferior ()->set_args (arg_view, (set_args == SET_ESC_ARGS));
+   }
   else
     {
       /* OK, that's all the options.  */
@@ -1400,7 +1419,8 @@ This is the GNU debugger.  Usage:\n\n\
   gdb_puts (_("\
 Selection of debuggee and its files:\n\n\
   --args             Arguments after executable-file are passed to inferior.\n\
-  --core=COREFILE    Analyze the core dump COREFILE.\n\
+  --no-escape-args   Like --args, but arguments are not escaped.\n                                                     \
+  --core=COREFILE    Analyze the core dump COREFILE.\n \
   --exec=EXECFILE    Use EXECFILE as the executable.\n\
   --pid=PID          Attach to running process PID.\n\
   --directory=DIR    Search for source files in DIR.\n\
index 36bfee2fa4b8af4260a1baf7eb068bc57f45ef3d..b8596608439ff2d072f9f39707afcf4bcafe2e0d 100644 (file)
@@ -33,42 +33,87 @@ if {[build_executable $testfile.exp $testfile $srcfile] == -1} {
 # NAME is the name to use for the tests and ARGLIST is the list of
 # arguments that are passed to GDB when it is started.
 #
-# The optional RE_LIST is the list of patterns to check the arguments
-# against, these patterns should match ARGLIST.  If the arguments are
-# expected to show up unmodified in the test output then RE_LIST can
-# be dropped, and this proc will reuse ARGLIST.
-
-proc args_test { name arglist {re_list {}} } {
-
-    # If RE_LIST is not supplied then we can reuse ARGLIST, this
-    # implies that the arguments will appear unmodified in the test
-    # output.
-    if {[llength $re_list] == 0} {
-       set re_list $arglist
+# The optional RE_ESC_LIST is the list of patterns to check the
+# inferior arguments against when GDB is started using --args.  If
+# RE_ESC_LIST is not given then ARGLIST is reused, this implies that
+# the inferior arguments appear unchanged in the test output.
+#
+# The optional RE_NO_ESC_LIST is the list of patterns to check the
+# inferior arguments against when GDB is started using
+# --no-escape-args.  If RE_NO_ESC_LIST is not given then RE_ESC_LIST
+# is reused, this implies that there's no difference between the test
+# output when the arguments are escaped or not.
+
+proc args_test { name arglist {re_esc_list {}} {re_no_esc_list {}} } {
+
+    # If either of the two regexp lists are not specificed then we can
+    # use an earlier argument value instead.
+    #
+    # For the first regexp list, if this is missing then we use the
+    # argument list, this assumes that the arguments will appear
+    # unmodified in the output.
+    if {[llength $re_esc_list] == 0} {
+       set re_esc_list $arglist
     }
 
-    foreach_with_prefix startup_with_shell { on off } {
-       save_vars { ::GDBFLAGS } {
-           set ::GDBFLAGS "$::GDBFLAGS --args $::binfile $arglist"
-
-           clean_restart
-           gdb_load $::binfile
-
-           gdb_test_no_output "set startup-with-shell ${startup_with_shell}" \
-               "set startup-with-shell for $name"
-
-           runto_main
-           gdb_breakpoint [gdb_get_line_number "set breakpoint here"]
-           gdb_continue_to_breakpoint "breakpoint for $name"
-
-           set expected_len [expr 1 + [llength $re_list]]
-           gdb_test "print argc" "\\\$$::decimal = $expected_len" "argc for $name"
+    # If the second regexp list is missing then we reuse the first
+    # regexp list.  This assumes there's no difference between escaped
+    # and unescaped arguments in the output.
+    if {[llength $re_no_esc_list] == 0} {
+       set re_no_esc_list $re_esc_list
+    }
 
-           set i 1
-           foreach arg $re_list {
-               gdb_test "print argv\[$i\]" "\\\$$::decimal = $::hex \"$arg\"" \
-                   "argv\[$i\] for $name"
-               set i [expr $i + 1]
+    foreach_with_prefix startup_with_shell { on off } {
+       foreach_with_prefix arg_flag { args no-escape-args } {
+           save_vars { ::GDBFLAGS } {
+               set ::GDBFLAGS "$::GDBFLAGS --${arg_flag} $::binfile $arglist"
+
+               clean_restart $::testfile
+
+               gdb_test_no_output \
+                   "set startup-with-shell ${startup_with_shell}" \
+                   "set startup-with-shell for $name"
+
+               runto_main
+               gdb_breakpoint [gdb_get_line_number "set breakpoint here"]
+               gdb_continue_to_breakpoint "breakpoint for $name"
+
+               if { $arg_flag eq "args" || $startup_with_shell eq "off" } {
+                   set re_list $re_esc_list
+               } else {
+                   set re_list $re_no_esc_list
+               }
+
+               set expected_len [expr 1 + [llength $re_list]]
+               gdb_test "print argc" \
+                   "\\\$$::decimal = $expected_len" "argc for $name"
+
+               set i 1
+               foreach arg $re_list {
+                   if { $arg eq "\n" } {
+                       set arg {\\n}
+                   } elseif { $arg eq "\"" } {
+                       set arg {\\\"}
+                   }
+
+                   # If we are starting with a shell, we're not escaping
+                   # special shell characters in arguments passed to GDB,
+                   # we're using a remote target, and the arguments contain
+                   # a shell variable (indicated with a '$'), then this
+                   # test will currently fail if we are splitting the
+                   # arguments.
+                   if { $startup_with_shell eq "on"
+                        && $arg_flag eq "no-escape-args"
+                        && [gdb_protocol_is_remote]
+                        && [string first "\$" $arglist] != -1 } {
+                       setup_xfail "*-*-*" gdb/28392
+                   }
+
+                   gdb_test "print argv\[$i\]" \
+                       "\\\$$::decimal = $::hex \"$arg\"" \
+                       "argv\[$i\] for $name"
+                   set i [expr $i + 1]
+               }
            }
        }
     }
@@ -102,3 +147,8 @@ args_test "two newlines" {{1} "\n" "\n" {3}} {1 \\\\n \\\\n 3}
 args_test "lone single quote" {{1} \' {3}}
 
 args_test "lone double quote" {{1} \" {3}} {1 \\\\\" 3}
+
+save_vars { ::env(TEST) } {
+    set ::env(TEST) "ABCD"
+    args_test "shell variable" {{$TEST}} {\\$TEST} {{ABCD}}
+}
diff --git a/gdb/testsuite/gdb.server/inferior-args.c b/gdb/testsuite/gdb.server/inferior-args.c
new file mode 100644 (file)
index 0000000..3bc3ff7
--- /dev/null
@@ -0,0 +1,27 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023-2025 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+  for (int i = 0; i < argc; i++)
+    printf ("[%d] %s\n", i, argv[i]);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.server/inferior-args.exp b/gdb/testsuite/gdb.server/inferior-args.exp
new file mode 100644 (file)
index 0000000..2ee26c5
--- /dev/null
@@ -0,0 +1,156 @@
+# Copyright 2023-2025 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test passing inferior arguments on the gdbserver command line.  Tests the
+# flags --no-startup-with-shell and --no-escape-args that change how GDB
+# interprets the arguments being passed.
+
+# This test relies on starting gdbserver using the pipe syntax.  Not sure
+# how well this will run if part of this test is being run elsewhere.
+require {!is_remote target} {!is_remote host}
+
+load_lib gdbserver-support.exp
+
+standard_testfile
+
+require allow_gdbserver_tests
+
+set gdbserver [find_gdbserver]
+if { $gdbserver == "" } {
+    unsupported "could not find gdbserver"
+    return
+}
+
+standard_testfile
+if {[build_executable "failed to prepare" $testfile $srcfile]} {
+    return -1
+}
+
+# EXTENDED_P is a boolean, when true gdbserver is started with --multi, and
+# GDB connects using extended-remote protocol.  Otherwise, no --multi flag
+# is passed, and GDB connects with the remote protocol.
+#
+# WITH_SHELL_P is a boolean, when true gdbserver starts the inferior using a
+# shell, when false gdbserver is passed the --no-startup-with-shell command
+# line option, and should not start the inferior through a shell.
+#
+# ESCAPE_P is a boolean, when true gdbserver applies escapes to the inferior
+# arguments, when false gdbserver is passed the --no-escape-args command
+# line option, and should not apply escaping to the inferior arguments.
+#
+# ARGLIST is a list of inferior arguments to add to the gdbserver command
+# line.
+#
+# RE_LIST is a list of patterns to match, one for each of ARGLIST.  Once the
+# inferior is started we check that each argument matches its corresponding
+# entry in RE_LIST.
+proc do_test_inner { extended_p with_shell_p escape_p arglist re_list } {
+
+    clean_restart ${::testfile}
+
+    gdb_test_no_output "set sysroot"
+
+    # Make sure we're disconnected, in case we're testing with an
+    # extended-remote board, therefore already connected.
+    gdb_test "disconnect" ".*"
+
+    if { $extended_p } {
+       set protocol "extended-remote"
+    } else {
+       set protocol "remote"
+    }
+
+    if { $escape_p } {
+       set esc_opt ""
+    } else {
+       set esc_opt "--no-escape-args"
+    }
+
+    if { $with_shell_p } {
+       set shell_opt ""
+    } else {
+       set shell_opt "--no-startup-with-shell"
+    }
+
+    gdb_test "target ${protocol} | ${::gdbserver} --once ${esc_opt} ${shell_opt} - ${::binfile} ${arglist}" \
+       ".*" \
+       "start gdbserver over stdin"
+
+    gdb_breakpoint main
+    gdb_continue_to_breakpoint main
+
+    set expected_len [expr 1 + [llength $re_list]]
+    gdb_test "print argc" \
+       "\\\$$::decimal = $expected_len" "check argc"
+
+    set i 1
+    foreach arg $re_list {
+       gdb_test "print argv\[$i\]" \
+           "\\\$$::decimal = $::hex \"$arg\"" \
+           "check argv\[$i\]"
+       set i [expr $i + 1]
+    }
+}
+
+# Wrapper around do_test_inner.  NAME is the name of this test, used to make
+# the test names unique.  ARGLIST is the list of inferior arguments to add
+# to the gdbserver command line.
+#
+# The optional RE_ESC_LIST is a list of patterns to match against the
+# inferior arguments once the inferior is started, one pattern for each
+# argument.  If RE_ESC_LIST is not given then ARGLIST is reused, which
+# implies the arguments appear unmodified in the test output.
+#
+# The optional RE_NO_ESC_LIST is a list of patterns ot match against the
+# inferior arguments when gdbserver is started with --no-escape-args or
+# --no-startup-with-shell.  There should be one pattern for each argument.
+# If RE_NO_ESC_LIST is not given then RE_ESC_LIST is resused, which implies
+# there's no difference in how the arguments are printed.
+proc args_test { name arglist {re_esc_list {}} {re_no_esc_list {}} } {
+    if {[llength $re_esc_list] == 0} {
+       set re_esc_list $arglist
+    }
+
+    if {[llength $re_no_esc_list] == 0} {
+       set re_no_esc_list $re_esc_list
+    }
+
+    foreach_with_prefix extended_p { yes no } {
+       foreach_with_prefix startup_with_shell { on off } {
+           foreach_with_prefix escape_p { yes no } {
+               if { $escape_p || !$startup_with_shell } {
+                   set re_list $re_esc_list
+               } else {
+                   set re_list $re_no_esc_list
+               }
+
+               with_test_prefix "$name" {
+                   do_test_inner $extended_p $startup_with_shell \
+                       $escape_p $arglist $re_list
+               }
+           }
+       }
+    }
+}
+
+args_test "basic" {a b c}
+args_test "one empty" {1 "" 3}
+args_test "two empty" {1 "" "" 3}
+args_test "one with single quotes" {1 "''" 3}
+args_test "lone double quote" {"1" \" 3} {1 \\\\\" 3}
+save_vars { env(TEST) } {
+    set env(TEST) "ABCD"
+    args_test "shell variable" {\$TEST} {\\$TEST} {ABCD}
+}
index 6dc805abd4d64a011b7ea9028247854a86503c5a..1f910a415b6ea5e3748bb2a05cde89f8083f6c46 100644 (file)
@@ -3851,10 +3851,20 @@ gdbserver_usage (FILE *stream)
           "  --startup-with-shell\n"
           "                        Start PROG using a shell.  I.e., execs a shell that\n"
           "                        then execs PROG.  (default)\n"
+          "                        To make use of globbing and variable subsitution for\n"
+          "                        arguments passed directly on gdbserver invocation,\n"
+          "                        see the --no-escape-args command line option in\n"
+          "                        addition\n"
           "  --no-startup-with-shell\n"
           "                        Exec PROG directly instead of using a shell.\n"
-          "                        Disables argument globbing and variable substitution\n"
-          "                        on UNIX-like systems.\n"
+          "  --no-escape-args\n"
+          "                        If PROG is started using a shell (see the\n"
+          "                        --[no-]startup-with-shell option),\n"
+          "                        ARGS passed directly on gdbserver invocation are\n"
+          "                        escaped, so no globbing or variable substitution\n"
+          "                        happens for those. This option disables escaping, so\n"
+          "                        globbing and variable substituation in the shell\n"
+          "                        are done for ARGS on UNIX-like systems.\n"
           "\n"
           "Debug options:\n"
           "\n"
@@ -4110,6 +4120,7 @@ captured_main (int argc, char *argv[])
   volatile bool multi_mode = false;
   volatile bool attach = false;
   bool selftest = false;
+  bool escape_args = true;
 #if GDB_SELF_TEST
   std::vector<const char *> selftest_filters;
 
@@ -4132,7 +4143,7 @@ captured_main (int argc, char *argv[])
     OPT_DEBUG, OPT_DEBUG_FILE, OPT_DEBUG_FORMAT, OPT_DISABLE_PACKET,
     OPT_DISABLE_RANDOMIZATION, OPT_NO_DISABLE_RANDOMIZATION,
     OPT_STARTUP_WITH_SHELL, OPT_NO_STARTUP_WITH_SHELL, OPT_ONCE,
-    OPT_SELFTEST,
+    OPT_SELFTEST, OPT_NO_ESCAPE
   };
 
   static struct option longopts[] =
@@ -4157,6 +4168,7 @@ captured_main (int argc, char *argv[])
        OPT_NO_STARTUP_WITH_SHELL},
       {"once", no_argument, nullptr, OPT_ONCE},
       {"selftest", optional_argument, nullptr, OPT_SELFTEST},
+      {"no-escape-args", no_argument, nullptr, OPT_NO_ESCAPE},
       {nullptr, no_argument, nullptr, 0}
     };
 
@@ -4360,6 +4372,10 @@ captured_main (int argc, char *argv[])
          }
          break;
 
+       case OPT_NO_ESCAPE:
+         escape_args = false;
+         break;
+
        case '?':
          /* Figuring out which element of ARGV contained the invalid
             argument is not simple.  There are a couple of cases we need
@@ -4475,7 +4491,8 @@ captured_main (int argc, char *argv[])
 
       int n = argc - (next_arg - argv);
       program_args
-       = construct_inferior_arguments ({&next_arg[1], &next_arg[n]}, true);
+       = construct_inferior_arguments ({&next_arg[1], &next_arg[n]},
+                                       escape_args);
 
       /* Wait till we are at first instruction in program.  */
       target_create_inferior (program_path.get (), program_args);