]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: allow 'set args' and run commands to contain newlines
authorAndrew Burgess <aburgess@redhat.com>
Wed, 15 Nov 2023 16:36:09 +0000 (16:36 +0000)
committerAndrew Burgess <aburgess@redhat.com>
Tue, 23 Dec 2025 11:30:42 +0000 (11:30 +0000)
When starting GDB it is possible to set an inferior argument that
contains a newline, for example:

  shell> gdb --args my.app "abc
  > def"
  ...
  (gdb) show args
  Argument list to give program being debugged when it is started is "abc'
  'def".

However, once GDB is started, the only way to install an argument
containing a newline is to use the Python API.

This commit changes that.

After this commit 'set args' as well as 'run', 'start', and 'starti',
will now accept multi-line inferior arguments, e.g.:

  (gdb) set args "abc
  > def"
  (gdb) show args
  Argument list to give program being debugged when it is started is ""abc
  def"".

And also:

  (gdb) run "abc
  > def"
  ... etc ...

Once GDB has presented the secondary prompt to gather the remaining
inferior arguments then it is possible for the user to quit argument
entry by sending SIGINT (usually, Ctrl-c), or sending EOF (usually,
Ctrl-d).  For the 'set args' case this will abort the argument change,
leaving the arguments as they were previously.  For the run style
commands, this aborts the run command completely, the inferior is not
changed, and the partially collected arguments are not installed.

On Unix hosts, arguments can be wrapped with either single or double
quotes, while on MS-Windows hosts, arguments can only be wrapped with
double quotes.  This gives the expected behaviour when native
debugging, but isn't entirely accurate.  If a user is cross debugging
between Unix and MS-Windows then the host machine will determine which
set of quotes is valid, which will then be incorrect for the actual
target machine.  This should probably be fixed in the future, but
isn't something I plan to fix immediately.  If this patch is accepted,
then I can create a bug to track this issue.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Tested-By: Guinevere Larsen <guinevere@redhat.com>
gdb/NEWS
gdb/doc/gdb.texinfo
gdb/infcmd.c
gdb/testsuite/gdb.base/inferior-args.exp

index ba37dc89f763214dc37294eb621dd2b25180a7dc..3a7f80649d0b25e62a1918a260e0ae30ab2d7fbe 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -99,6 +99,14 @@ single-inf-arg in qSupported
   reply with the single-inf-arg feature to indicate that it is able to
   accept arguments as a single string.
 
+set args
+run
+start
+starti
+  These commands now all allow for entering inferior arguments that
+  contain a newline character.  The newline must be contained within a
+  single or double quoted argument.
+
 * New remote packets
 
 qExecAndArgs
index 61e43d971fb28a920b3a558cae2fd78986a7c81c..61e6dfe84e46dee76f867e69f68e8045d7fe3b7e 100644 (file)
@@ -2981,6 +2981,32 @@ with no arguments.  Once you have run your program with arguments,
 using @code{set args} before the next @code{run} is the only way to run
 it again without arguments.
 
+@cindex newlines in inferior command lines
+@cindex inferior command line with embedded newlines
+It is possible to set arguments containing a newline character.  This
+can be done by enclosing an argument within a quotes.  For example,
+start by entering:
+
+@smallexample
+(@value{GDBP}) set args "ab
+@end smallexample
+
+@noindent
+and then enter the newline, @value{GDBN} gives a secondary prompt
+@code{>} and allows you to continue entering the arguments:
+
+@smallexample
+(@value{GDBP}) set args "ab
+>cd"
+(@value{GDBP}) show args
+Argument list to give program being debugged when it is started is ""ab
+cd"".
+@end smallexample
+
+On Unix systems both single or double quotes can be used around
+arguments containing a newline.  On MS-Windows, only double quotes can
+be used.
+
 @kindex show args
 @item show args
 Show the arguments to give your program when it is started.
index 3e19bb8c1d521def3f6ba64e004173441e1316f9..875bbe1ee692b040e395e4cc45091f70adbc621e 100644 (file)
@@ -116,11 +116,129 @@ show_inferior_tty_command (struct ui_file *file, int from_tty,
                "is \"%s\".\n"), value);
 }
 
+/* Return true if the inferior argument string ARGS represents a "complete"
+   set of arguments.  Arguments are considered complete so long as they
+   don't contain unbalanced single or double quoted strings.  Unbalanced
+   means that a single or double quoted argument is started, but not
+   finished.  */
+
+static bool
+args_complete_p (const std::string &args)
+{
+  const char *input = args.c_str ();
+  bool squote = false, dquote = false;
+
+  while (*input != '\0')
+    {
+      input = skip_spaces (input);
+
+      if (squote)
+       {
+         /* Inside a single quoted argument, look for the closing single
+            quote.  */
+         if (*input == '\'')
+           squote = false;
+       }
+      else if (dquote)
+       {
+         /* If we see either '\"' or '\\' within a double quoted argument
+            then skip both characters (one is skipped here, and the other
+            at the end of the loop).  We need to skip the '\"' so that we
+            don't consider the '"' as closing the double quoted argument,
+            and we don't skip the entire '\\' then we'll only skip the
+            first '\', in which case we might see the second '\' as a '\"'
+            sequence, which would be wrong.  */
+         if (*input == '\\' && strchr ("\"\\", *(input + 1)) != nullptr)
+           ++input;
+         /* Otherwise, just look for the closing double quote.  */
+         else if (*input == '"')
+           dquote = false;
+       }
+      else
+       {
+         /* Outside of either a single or double quoted argument, we need
+            to check for '\"', '\'', and '\\'.  The escaped quotes we
+            obviously need to skip so we don't think that we have started
+            a quoted argument.  The '\\' we need to skip so we don't just
+            skip the first '\' and then incorrectly consider the second
+            '\' are part of a '\"' or '\'' sequence.  */
+         if (*input == '\\' && strchr ("\"\\'", *(input + 1)) != nullptr)
+           ++input;
+         /* Otherwise, check for the start of a single or double quoted
+            argument.  Single quotes have no special meaning on Windows
+            hosts.  This is a little iffy as we are allowing the choice of
+            host to determine what is, or isn't a special character, when
+            really, this is a function of the target.  */
+#ifndef _WIN32
+         else if (*input == '\'')
+           squote = true;
+#endif
+         else if (*input == '"')
+           dquote = true;
+       }
+
+      ++input;
+    }
+
+  return (!dquote && !squote);
+}
+
+/* Build a complete inferior argument string (all arguments to pass to the
+   inferior) and return it.  ARGS is the initial part of the inferior
+   arguments string, which might be the complete inferior arguments, in
+   which case this function will immediately return ARGS.
+
+   If ARGS is not considered complete then a prompt is presented to the
+   user and they can continue to extend ARGS.  At the end of each line of
+   input the full value of ARGS is examined, if it is complete then the
+   full ARGS value is returned, otherwise, the user is given another prompt
+   and can continue to add to ARGS.
+
+   ARGS is considered incomplete if there are unbalanced, unquoted, double
+   or single quotes within ARGS.  */
+
+static std::string
+get_complete_args (std::string args)
+{
+  /* If the user wants an argument containing a newline then they need to
+     do so within quotes.  Use args_complete_p to check if the ARGS string
+     contains balanced double and single quotes.  If not then prompt the
+     user for additional arguments and append this to ARGS.  */
+  const char *prompt = nullptr;
+  while (!args_complete_p (args))
+    {
+      if (prompt == nullptr)
+       {
+         prompt = getenv ("PS2");
+         if (prompt == nullptr)
+           prompt = "> ";
+       }
+
+      std::string buffer;
+      const char *content = command_line_input (buffer, prompt, "set_args");
+      if (content == nullptr)
+       return {};
+
+      args += "\n" + buffer;
+    }
+
+  return args;
+}
+
 /* Store the new value passed to 'set args'.  */
 
 static void
-set_args_value (const std::string &args)
+set_args_value (const std::string &args_in)
 {
+  std::string args;
+
+  if (!args_in.empty ())
+    {
+      args = get_complete_args (args_in);
+      if (args.empty ())
+       return;
+    }
+
   current_inferior ()->set_args (args);
 }
 
@@ -369,6 +487,20 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
 
   dont_repeat ();
 
+  gdb::unique_xmalloc_ptr<char> stripped = strip_bg_char (args, &async_exec);
+
+  std::string inf_args;
+  /* If there were other args, beside '&', process them.  */
+  if (stripped != nullptr)
+    {
+      /* If ARGS is only a partial argument string then this call will
+        interactively read more arguments from the user.  If the user
+        quits then we shouldn't start the inferior.  */
+      inf_args = get_complete_args (stripped.get ());
+      if (inf_args.empty ())
+       return;
+    }
+
   scoped_disable_commit_resumed disable_commit_resumed ("running");
 
   kill_if_already_running (from_tty);
@@ -390,9 +522,6 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   reopen_exec_file ();
   reread_symbols (from_tty);
 
-  gdb::unique_xmalloc_ptr<char> stripped = strip_bg_char (args, &async_exec);
-  args = stripped.get ();
-
   /* Do validation and preparation before possibly changing anything
      in the inferior.  */
 
@@ -405,6 +534,9 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
 
   /* Done.  Can now set breakpoints, change inferior args, etc.  */
 
+  if (!inf_args.empty ())
+    current_inferior ()->set_args (inf_args);
+
   /* Insert temporary breakpoint in main function if requested.  */
   if (run_how == RUN_STOP_AT_MAIN)
     {
@@ -426,10 +558,6 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
      the user has to manually nuke all symbols between runs if they
      want them to go away (PR 2207).  This is probably reasonable.  */
 
-  /* If there were other args, beside '&', process them.  */
-  if (args != nullptr)
-    current_inferior ()->set_args (args);
-
   if (from_tty)
     {
       uiout->field_string (nullptr, "Starting program");
index 036dd209013558f9cf95f28315d33c2b9d9dca90..50e1ae287b2cce5b3a70f4b4bd6a86b0ae6a25c4 100644 (file)
@@ -211,12 +211,15 @@ lappend test_desc_list [list "test four" \
                            [list "$hex \"'\"" \
                                 "$hex \"\\\\\"\""]]
 
-# Run all tests in the global TEST_DESC_LIST.
+# Run all tests in the global TEST_DESC_LIST, as well as some tests of
+# inferior arguments containing newlines.
 proc run_all_tests {} {
+    set all_methods { "start" "starti" "run" "set args" }
+
     foreach desc $::test_desc_list {
        lassign $desc name stub_suitable args re_list
        with_test_prefix $name {
-           foreach_with_prefix set_method { "start" "starti" "run" "set args" } {
+           foreach_with_prefix set_method $all_methods {
                foreach_with_prefix startup_with_shell { on off } {
                    do_test $set_method $startup_with_shell $args $re_list \
                        $stub_suitable
@@ -224,6 +227,70 @@ proc run_all_tests {} {
            }
        }
     }
+
+    # Check the multi-line argument entry.  This isn't going to work when
+    # using the gdbstub, as the only way to set arguments in this case is
+    # via the gdbserver command line, which isn't what we're testing here.
+    if { ![use_gdb_stub] } {
+       foreach_with_prefix set_method $all_methods {
+           clean_restart $::testfile
+
+           # First check that we can abort entering multi-line arguments.
+           set saw_prompt false
+           gdb_test_multiple "$set_method \"ab" "abort argument entry" {
+               -re "^$set_method \"ab\r\n" {
+                   exp_continue
+               }
+               -re "^> $" {
+                   set saw_prompt true
+                   send_gdb "\004"
+                   exp_continue
+               }
+               -re "quit\r\n$::gdb_prompt $" {
+                   gdb_assert {$saw_prompt} \
+                       $gdb_test_name
+               }
+           }
+
+           # Now place a breakpoint on main.
+           if { ![gdb_breakpoint "main" message] } {
+               fail "could not set breakpoint on main"
+               continue
+           }
+
+           # And actually enter some multi-line arguments.
+           set saw_prompt false
+           gdb_test_multiple "$set_method \"xy" "complete argument entry" {
+               -re "^$set_method \"xy\r\n" {
+                   exp_continue
+               }
+               -re "^> $" {
+                   set saw_prompt true
+                   send_gdb "12\"\n"
+                   exp_continue
+               }
+
+               -re "$::gdb_prompt $" {
+                   gdb_assert { $saw_prompt } \
+                       $gdb_test_name
+               }
+           }
+
+           # For the two methods that don't automatically run to main,
+           # poke the inferior along to main.
+           if { $set_method == "set args" } {
+               if { ![runto_main] } {
+                   continue
+               }
+           } elseif { $set_method == "starti" } {
+               gdb_continue_to_breakpoint "b/p in main"
+           }
+
+           # And check we correctly see the argument containing a newline.
+           gdb_test "print argc" " = 2"
+           gdb_test "print argv\[1\]" " = $::hex \"xy\\\\n12\""
+       }
+    }
 }
 
 run_all_tests