[12/16] gdb/gdbserver: add a '--no-escape-args' command line option

Message ID 47fe194ee2e88d8ed68192abc31ac8e3df626fe1.1704809585.git.aburgess@redhat.com
State New
Headers
Series Inferior argument (inc for remote targets) changes |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed

Commit Message

Andrew Burgess Jan. 9, 2024, 2:26 p.m. UTC
  From: Michael Weghorn <m.weghorn@posteo.de>

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, however, 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 settings 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
---
 gdb/NEWS                                   |   8 ++
 gdb/doc/gdb.texinfo                        | 114 ++++++++++++++-
 gdb/main.c                                 |  28 +++-
 gdb/testsuite/gdb.base/args.exp            | 101 ++++++++-----
 gdb/testsuite/gdb.server/inferior-args.c   |  27 ++++
 gdb/testsuite/gdb.server/inferior-args.exp | 157 +++++++++++++++++++++
 gdbserver/server.cc                        |  21 ++-
 7 files changed, 407 insertions(+), 49 deletions(-)
 create mode 100644 gdb/testsuite/gdb.server/inferior-args.c
 create mode 100644 gdb/testsuite/gdb.server/inferior-args.exp
  

Comments

Eli Zaretskii Jan. 9, 2024, 4:43 p.m. UTC | #1
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Michael Weghorn <m.weghorn@posteo.de>, Andrew Burgess <aburgess@redhat.com>
> Date: Tue,  9 Jan 2024 14:26:35 +0000
> 
>  gdb/NEWS                                   |   8 ++
>  gdb/doc/gdb.texinfo                        | 114 ++++++++++++++-
>  gdb/main.c                                 |  28 +++-
>  gdb/testsuite/gdb.base/args.exp            | 101 ++++++++-----
>  gdb/testsuite/gdb.server/inferior-args.c   |  27 ++++
>  gdb/testsuite/gdb.server/inferior-args.exp | 157 +++++++++++++++++++++
>  gdbserver/server.cc                        |  21 ++-
>  7 files changed, 407 insertions(+), 49 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.server/inferior-args.c
>  create mode 100644 gdb/testsuite/gdb.server/inferior-args.exp

Thanks, the documentation parts are okay.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  
Keith Seitz Jan. 21, 2024, 3:57 a.m. UTC | #2
[I realize the documentation has already been approved.
Nonetheles, I noticed some typos.]

On 1/9/24 06:26, Andrew Burgess wrote:
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 9dbe53384e1..abb07d74baf 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -898,9 +898,9 @@
>   ``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 stops option processing.
>   @smallexample
>   @value{GDBP} --args gcc -O2 -c foo.c
>   @end smallexample
> @@ -1246,6 +1246,56 @@
>   executable file are passed as command line arguments to the inferior.
>   This option stops option processing.
>   
> +Argument supplied using @code{--args} will have backslashes applied to

I think this should be "Arguments".

> +escape any special shell characters.  This ensures that when the
> +inferior starts it is passed arguments exactly as @value{GDBN}

and a comma is needed after the introductory phrase, "when the
inferior starts."

> +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 in 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".

Is this supposed to be "\*.c"?

> +@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}
> @@ -50498,9 +50548,10 @@
>   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

"Change the"?

> +either of these options are passed as arguments to the inferior.  As
> +an example, take the following command:
>   
>   @smallexample
>   gdb ./a.out -q
> @@ -50515,7 +50566,44 @@
>   @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 argument 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},
> +but 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}.
> @@ -50857,6 +50945,18 @@
>   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

Need a comma between "option" and "special."

Keith

> +@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
> diff --git a/gdb/main.c b/gdb/main.c
> index 015ed396f58..e9cd4172e4a 100644
> --- a/gdb/main.c
> +++ b/gdb/main.c
> @@ -622,9 +622,10 @@ 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;
>   
> +  enum { NO_ARGS, SET_ESC_ARGS, SET_NO_ESC_ARGS } set_args = NO_ARGS;
> +
>     /* Pointers to various arguments from command line.  */
>     char *symarg = NULL;
>     char *execarg = NULL;
> @@ -773,7 +774,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.  */
> @@ -846,7 +849,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}
> @@ -858,7 +862,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.  */
> @@ -939,6 +943,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 ();
> @@ -1072,7 +1082,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
> @@ -1084,8 +1094,11 @@ captured_main_1 (struct captured_main_args *context)
>         symarg = argv[optind];
>         execarg = argv[optind];
>         ++optind;
> +      escape_args_func escape_func
> +	= ((set_args == SET_ESC_ARGS) ? escape_shell_characters
> +	   : escape_quotes_and_white_space);
>         gdb::array_view<char * const> arg_view (&argv[optind], argc - optind);
> -      current_inferior ()->set_args (arg_view, escape_shell_characters);
> +      current_inferior ()->set_args (arg_view, escape_func);
>       }
>     else
>       {
> @@ -1398,7 +1411,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\
> diff --git a/gdb/testsuite/gdb.base/args.exp b/gdb/testsuite/gdb.base/args.exp
> index 7c123e36404..9ff9e7ee6d1 100644
> --- a/gdb/testsuite/gdb.base/args.exp
> +++ b/gdb/testsuite/gdb.base/args.exp
> @@ -32,41 +32,73 @@ 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 $::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 $::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"
> +
> +		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 {\\\"}
> +		    }
> +		    gdb_test "print argv\[$i\]" \
> +			"\\\$$::decimal = $::hex \"$arg\"" \
> +			"argv\[$i\] for $name"
> +		    set i [expr $i + 1]
> +		}
>   	    }
>   	}
>       }
> @@ -102,6 +134,11 @@ proc run_all_tests {} {
>       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}}
> +    }
>   }
>   
>   run_all_tests
> diff --git a/gdb/testsuite/gdb.server/inferior-args.c b/gdb/testsuite/gdb.server/inferior-args.c
> new file mode 100644
> index 00000000000..5fd215f50a8
> --- /dev/null
> +++ b/gdb/testsuite/gdb.server/inferior-args.c
> @@ -0,0 +1,27 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023 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
> index 00000000000..9b2aeb249e0
> --- /dev/null
> +++ b/gdb/testsuite/gdb.server/inferior-args.exp
> @@ -0,0 +1,157 @@
> +# Copyright 2023 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 ${::binfile}
> +
> +    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 {
> +	verbose -log "APB ($arg)"
> +	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}
> +}
> diff --git a/gdbserver/server.cc b/gdbserver/server.cc
> index 65df03ef309..0445fa0237f 100644
> --- a/gdbserver/server.cc
> +++ b/gdbserver/server.cc
> @@ -3837,10 +3837,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"
> @@ -4074,6 +4084,7 @@ captured_main (int argc, char *argv[])
>     volatile int attach = 0;
>     int was_running;
>     bool selftest = false;
> +  bool escape_args = true;
>   #if GDB_SELF_TEST
>     std::vector<const char *> selftest_filters;
>   
> @@ -4230,6 +4241,8 @@ captured_main (int argc, char *argv[])
>   	startup_with_shell = true;
>         else if (strcmp (*next_arg, "--no-startup-with-shell") == 0)
>   	startup_with_shell = false;
> +      else if (strcmp (*next_arg, "--no-escape-args") == 0)
> +	escape_args = false;
>         else if (strcmp (*next_arg, "--once") == 0)
>   	run_once = true;
>         else if (strcmp (*next_arg, "--selftest") == 0)
> @@ -4339,8 +4352,10 @@ captured_main (int argc, char *argv[])
>         std::vector<char *> temp_arg_vector;
>         for (i = 1; i < n; i++)
>   	temp_arg_vector.push_back (next_arg[i]);
> +      escape_args_func escape_func = (escape_args ? escape_shell_characters
> +				      : escape_quotes_and_white_space);
>         program_args = construct_inferior_arguments (temp_arg_vector,
> -						   escape_shell_characters);
> +						   escape_func);
>   
>         /* Wait till we are at first instruction in program.  */
>         target_create_inferior (program_path.get (), program_args);
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 65a082808e4..80c766eeeda 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -9,6 +9,14 @@ 
 * GDB index now contains information about the main function.  This speeds up
   startup when it is being used for some large binaries.
 
+* 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.
+
 * Changed commands
 
 disassemble
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 9dbe53384e1..abb07d74baf 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -898,9 +898,9 @@ 
 ``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 stops option processing.
 @smallexample
 @value{GDBP} --args gcc -O2 -c foo.c
 @end smallexample
@@ -1246,6 +1246,56 @@ 
 executable file are passed as command line arguments to the inferior.
 This option stops option processing.
 
+Argument 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 in 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}
@@ -50498,9 +50548,10 @@ 
 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
@@ -50515,7 +50566,44 @@ 
 @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 argument 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},
+but 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}.
@@ -50857,6 +50945,18 @@ 
 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
diff --git a/gdb/main.c b/gdb/main.c
index 015ed396f58..e9cd4172e4a 100644
--- a/gdb/main.c
+++ b/gdb/main.c
@@ -622,9 +622,10 @@  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;
 
+  enum { NO_ARGS, SET_ESC_ARGS, SET_NO_ESC_ARGS } set_args = NO_ARGS;
+
   /* Pointers to various arguments from command line.  */
   char *symarg = NULL;
   char *execarg = NULL;
@@ -773,7 +774,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.  */
@@ -846,7 +849,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}
@@ -858,7 +862,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.  */
@@ -939,6 +943,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 ();
@@ -1072,7 +1082,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
@@ -1084,8 +1094,11 @@  captured_main_1 (struct captured_main_args *context)
       symarg = argv[optind];
       execarg = argv[optind];
       ++optind;
+      escape_args_func escape_func
+	= ((set_args == SET_ESC_ARGS) ? escape_shell_characters
+	   : escape_quotes_and_white_space);
       gdb::array_view<char * const> arg_view (&argv[optind], argc - optind);
-      current_inferior ()->set_args (arg_view, escape_shell_characters);
+      current_inferior ()->set_args (arg_view, escape_func);
     }
   else
     {
@@ -1398,7 +1411,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\
diff --git a/gdb/testsuite/gdb.base/args.exp b/gdb/testsuite/gdb.base/args.exp
index 7c123e36404..9ff9e7ee6d1 100644
--- a/gdb/testsuite/gdb.base/args.exp
+++ b/gdb/testsuite/gdb.base/args.exp
@@ -32,41 +32,73 @@  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 $::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 $::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"
+
+		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 {\\\"}
+		    }
+		    gdb_test "print argv\[$i\]" \
+			"\\\$$::decimal = $::hex \"$arg\"" \
+			"argv\[$i\] for $name"
+		    set i [expr $i + 1]
+		}
 	    }
 	}
     }
@@ -102,6 +134,11 @@  proc run_all_tests {} {
     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}}
+    }
 }
 
 run_all_tests
diff --git a/gdb/testsuite/gdb.server/inferior-args.c b/gdb/testsuite/gdb.server/inferior-args.c
new file mode 100644
index 00000000000..5fd215f50a8
--- /dev/null
+++ b/gdb/testsuite/gdb.server/inferior-args.c
@@ -0,0 +1,27 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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
index 00000000000..9b2aeb249e0
--- /dev/null
+++ b/gdb/testsuite/gdb.server/inferior-args.exp
@@ -0,0 +1,157 @@ 
+# Copyright 2023 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 ${::binfile}
+
+    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 {
+	verbose -log "APB ($arg)"
+	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}
+}
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index 65df03ef309..0445fa0237f 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -3837,10 +3837,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"
@@ -4074,6 +4084,7 @@  captured_main (int argc, char *argv[])
   volatile int attach = 0;
   int was_running;
   bool selftest = false;
+  bool escape_args = true;
 #if GDB_SELF_TEST
   std::vector<const char *> selftest_filters;
 
@@ -4230,6 +4241,8 @@  captured_main (int argc, char *argv[])
 	startup_with_shell = true;
       else if (strcmp (*next_arg, "--no-startup-with-shell") == 0)
 	startup_with_shell = false;
+      else if (strcmp (*next_arg, "--no-escape-args") == 0)
+	escape_args = false;
       else if (strcmp (*next_arg, "--once") == 0)
 	run_once = true;
       else if (strcmp (*next_arg, "--selftest") == 0)
@@ -4339,8 +4352,10 @@  captured_main (int argc, char *argv[])
       std::vector<char *> temp_arg_vector;
       for (i = 1; i < n; i++)
 	temp_arg_vector.push_back (next_arg[i]);
+      escape_args_func escape_func = (escape_args ? escape_shell_characters
+				      : escape_quotes_and_white_space);
       program_args = construct_inferior_arguments (temp_arg_vector,
-						   escape_shell_characters);
+						   escape_func);
 
       /* Wait till we are at first instruction in program.  */
       target_create_inferior (program_path.get (), program_args);