From patchwork Tue Jan 9 14:26:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 83654 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id D45933858407 for ; Tue, 9 Jan 2024 14:32:50 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 828073858CDB for ; Tue, 9 Jan 2024 14:27:05 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 828073858CDB Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 828073858CDB Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1704810431; cv=none; b=EdnoB4jLm5JzufAoHWiV3NU5jGl7pHJttaE0oPU/MZABXZvsbVPt2rf+hi30XnhWojRlDnPQXsGq0ld9qQgcAVGkwI1eCs8gEKIblXlGDi1zUR2wccjLYWjnby7tVCCLPiSBLB3Ztrn14OpSM73ZMkK2dhZ5H5+qRZZvvn+9Uew= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1704810431; c=relaxed/simple; bh=g6ACxEyGo5MQVH0f+Mkxa5WLwKRjx9W+/xeoGSGxPNc=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=yFNLuhmxTAvHzV46h2ABlsaxfk5AGEEhNax/tFPvjijCH6gME8dhFx4wiwuprU/jGKtozXSqekflDFvWfozLyLi6wUfiNxZlEDYRYeXeUb7PqjrB7y4bNfXME/d1ANpwVsqPXQ+zDaCkx78z02ihKiOelcNrDl5FD2tEaxNFYr0= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1704810425; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=rL4p13QpFaFjZKjfeUnpU44cAF7n/8oufum9jxEwzSg=; b=NUUFyxpwEZkp9B+vTWJyxB3kHryEFzjoqXiPbZgLVd00ksPimSATbFyXx2ia0l2snmsrn5 rJ/Jp+2OxB2o6ZphylLcjqaxhl0c3a6rpB7u5swqoHlhuP+ykHralGtanCtSGBajWdOph0 kP7Ne4fd5Hkv/4vHcfe0N/UsO2+P1bQ= Received: from mail-wm1-f70.google.com (mail-wm1-f70.google.com [209.85.128.70]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-534--1QGZOyhNPSL8mgPel0IJg-1; Tue, 09 Jan 2024 09:27:03 -0500 X-MC-Unique: -1QGZOyhNPSL8mgPel0IJg-1 Received: by mail-wm1-f70.google.com with SMTP id 5b1f17b1804b1-40e47b2f6b8so14014285e9.2 for ; Tue, 09 Jan 2024 06:27:03 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1704810422; x=1705415222; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=rL4p13QpFaFjZKjfeUnpU44cAF7n/8oufum9jxEwzSg=; b=jHMB6Dh/kBifxvxuvK5kIDd4mXj+mc83VkGhwoafOD6NQqlFK/Y3z8nJ7DqKW2i+0t cDrIelAF9dzxI4lbbn5uK9jZdTbpEgnJx3AXFH2phyC7vah90o8N9Q95RqyQ6PNdjuV5 bUtDYppBvllHG41GMM4irxjWhCgBZ4IQzcff2C8oS0TDJkPgOtK6Reetdfk7L0OKFYce 9F7Ix6YLrjxvZFf2UdV+nPq9wvq6ZuJK4BgbDyaZUw9GSUYb2PqAS3SaQBIWhKrKio/C zcBNoOumVeilXAund0cwbYY94KEzQz9jAbO7rCWUPzSvBKja7OlZmULOe0179cge5X7W 4G5Q== X-Gm-Message-State: AOJu0YwOVqu/ssjybq7B3+dtaKW1MIhY/t51EDZ593OY3gUcFCOioiGJ pW4/eMxF34qfTZ2gRcujELNPDQHyjkX/zSOL0RoRI4GD2jMt3gZ1cX9ShrN73SKYouiz3hug2Yw oaRayx75D6n2Wi0fLmBPgWFH1xp8W5pbfpbeVLGdWA2WH3tqwf7A03drdOYHE748t8fkDP0Hdqv aGcp0J2e7sRLpwMA== X-Received: by 2002:a05:600c:a007:b0:40e:5333:2024 with SMTP id jg7-20020a05600ca00700b0040e53332024mr29859wmb.53.1704810421388; Tue, 09 Jan 2024 06:27:01 -0800 (PST) X-Google-Smtp-Source: AGHT+IFC3PA8oXHov9l8MdSVDNYpV6L5r9304Hhb3fD1nr3h1Zs22vbr12gKbATBa2QFTesEA7Zryg== X-Received: by 2002:a05:600c:a007:b0:40e:5333:2024 with SMTP id jg7-20020a05600ca00700b0040e53332024mr29844wmb.53.1704810420666; Tue, 09 Jan 2024 06:27:00 -0800 (PST) Received: from localhost (185.223.159.143.dyn.plus.net. [143.159.223.185]) by smtp.gmail.com with ESMTPSA id p3-20020a05600c358300b0040d91930f93sm3733546wmq.11.2024.01.09.06.27.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 09 Jan 2024 06:27:00 -0800 (PST) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Michael Weghorn , Andrew Burgess Subject: [PATCH 12/16] gdb/gdbserver: add a '--no-escape-args' command line option Date: Tue, 9 Jan 2024 14:26:35 +0000 Message-Id: <47fe194ee2e88d8ed68192abc31ac8e3df626fe1.1704809585.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-13.4 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces+patchwork=sourceware.org@sourceware.org From: Michael Weghorn 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 ' or 'gdbserver ', 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 ', 'run ', 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 '", 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 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28392 Reviewed-By: Eli Zaretskii --- 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 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 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 . */ + +#include + +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 . + +# 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 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 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);