diff --git a/gdb/remote.c b/gdb/remote.c
index 66c58c884d3..64d11e19ff2 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -80,6 +80,7 @@
 #include "async-event.h"
 #include "gdbsupport/selftest.h"
 #include "cli/cli-style.h"
+#include "gdbsupport/remote-args.h"
 
 /* The remote target.  */
 
@@ -10835,16 +10836,15 @@ remote_target::extended_remote_run (const std::string &args)
 
   if (!args.empty ())
     {
-      int i;
+      std::vector<std::string> split_args = gdb::remote_args::split (args);
 
-      gdb_argv argv (args.c_str ());
-      for (i = 0; argv[i] != NULL; i++)
+      for (const auto &a : split_args)
 	{
-	  if (strlen (argv[i]) * 2 + 1 + len >= get_remote_packet_size ())
+	  if (a.size () * 2 + 1 + len >= get_remote_packet_size ())
 	    error (_("Argument list too long for run packet"));
 	  rs->buf[len++] = ';';
-	  len += 2 * bin2hex ((gdb_byte *) argv[i], rs->buf.data () + len,
-			      strlen (argv[i]));
+	  len += 2 * bin2hex ((gdb_byte *) a.c_str (), rs->buf.data () + len,
+			      a.size ());
 	}
     }
 
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index def01c1ee80..f4feb5906c4 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -51,6 +51,7 @@
 #include "gdbsupport/scoped_restore.h"
 #include "gdbsupport/search.h"
 #include "gdbsupport/gdb_argv_vec.h"
+#include "gdbsupport/remote-args.h"
 
 /* PBUFSIZ must also be at least as big as IPA_CMD_BUF_SIZE, because
    the client state data is passed directly to some agent
@@ -3465,7 +3466,7 @@ handle_v_run (char *own_buf)
   else
     program_path.set (new_program_name.get ());
 
-  program_args = construct_inferior_arguments (new_argv.get (), true);
+  program_args = gdb::remote_args::join (new_argv.get ());
 
   try
     {
diff --git a/gdbsupport/Makefile.am b/gdbsupport/Makefile.am
index e77298751cd..9e7def7b830 100644
--- a/gdbsupport/Makefile.am
+++ b/gdbsupport/Makefile.am
@@ -80,6 +80,7 @@ libgdbsupport_a_SOURCES = \
     pathstuff.cc \
     print-utils.cc \
     ptid.cc \
+    remote-args.cc \
     rsp-low.cc \
     run-time-clock.cc \
     safe-strerror.cc \
diff --git a/gdbsupport/Makefile.in b/gdbsupport/Makefile.in
index db3d6f6b4dd..190b16f767e 100644
--- a/gdbsupport/Makefile.in
+++ b/gdbsupport/Makefile.in
@@ -163,12 +163,12 @@ am_libgdbsupport_a_OBJECTS = agent.$(OBJEXT) btrace-common.$(OBJEXT) \
 	gdb_tilde_expand.$(OBJEXT) gdb_wait.$(OBJEXT) \
 	gdb_vecs.$(OBJEXT) job-control.$(OBJEXT) netstuff.$(OBJEXT) \
 	new-op.$(OBJEXT) osabi.$(OBJEXT) pathstuff.$(OBJEXT) \
-	print-utils.$(OBJEXT) ptid.$(OBJEXT) rsp-low.$(OBJEXT) \
-	run-time-clock.$(OBJEXT) safe-strerror.$(OBJEXT) \
-	scoped_mmap.$(OBJEXT) search.$(OBJEXT) signals.$(OBJEXT) \
-	signals-state-save-restore.$(OBJEXT) task-group.$(OBJEXT) \
-	tdesc.$(OBJEXT) thread-pool.$(OBJEXT) xml-utils.$(OBJEXT) \
-	$(am__objects_1) $(am__objects_2)
+	print-utils.$(OBJEXT) ptid.$(OBJEXT) remote-args.$(OBJEXT) \
+	rsp-low.$(OBJEXT) run-time-clock.$(OBJEXT) \
+	safe-strerror.$(OBJEXT) scoped_mmap.$(OBJEXT) search.$(OBJEXT) \
+	signals.$(OBJEXT) signals-state-save-restore.$(OBJEXT) \
+	task-group.$(OBJEXT) tdesc.$(OBJEXT) thread-pool.$(OBJEXT) \
+	xml-utils.$(OBJEXT) $(am__objects_1) $(am__objects_2)
 libgdbsupport_a_OBJECTS = $(am_libgdbsupport_a_OBJECTS)
 AM_V_P = $(am__v_P_@AM_V@)
 am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
@@ -438,6 +438,7 @@ libgdbsupport_a_SOURCES = \
     pathstuff.cc \
     print-utils.cc \
     ptid.cc \
+    remote-args.cc \
     rsp-low.cc \
     run-time-clock.cc \
     safe-strerror.cc \
@@ -548,6 +549,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pathstuff.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print-utils.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ptid.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remote-args.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rsp-low.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run-time-clock.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/safe-strerror.Po@am__quote@
diff --git a/gdbsupport/remote-args.cc b/gdbsupport/remote-args.cc
new file mode 100644
index 00000000000..2493433cc62
--- /dev/null
+++ b/gdbsupport/remote-args.cc
@@ -0,0 +1,43 @@
+/* Copyright (C) 2023-2025 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 "gdbsupport/common-defs.h"
+#include "gdbsupport/remote-args.h"
+#include "gdbsupport/common-inferior.h"
+#include "gdbsupport/buildargv.h"
+
+/* See remote-args.h.  */
+
+std::vector<std::string>
+gdb::remote_args::split (const std::string &args)
+{
+  std::vector<std::string> results;
+
+  gdb_argv argv (args.c_str ());
+  for (int i = 0; argv[i] != nullptr; i++)
+    results.emplace_back (argv[i]);
+
+  return results;
+}
+
+/* See remote-args.h.  */
+
+std::string
+gdb::remote_args::join (const std::vector<char *> &args)
+{
+  return construct_inferior_arguments (args, true);
+}
diff --git a/gdbsupport/remote-args.h b/gdbsupport/remote-args.h
new file mode 100644
index 00000000000..93dbdc6bc96
--- /dev/null
+++ b/gdbsupport/remote-args.h
@@ -0,0 +1,55 @@
+/* Functions to help when passing arguments between GDB and gdbserver.
+
+   Copyright (C) 2023-2025 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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/>.  */
+
+/* The functions declared here are used when passing inferior arguments
+   from GDB to gdbserver.
+
+   The remote protocol requires that arguments are passed as a vector of
+   separate argument while GDB stores the arguments as a single string, and
+   gdbserver also requires the arguments be a single string.
+
+   These functions then provide a mechanism to split up an argument string
+   and recombine it within gdbserver while preserving escaping of special
+   characters within the argument string.  */
+
+namespace gdb
+{
+
+namespace remote_args
+{
+
+/* ARGS is an inferior argument string.  This function splits ARGS into
+   individual arguments and returns a vector containing each argument.  */
+
+extern std::vector<std::string> split (const std::string &args);
+
+/* Join together the separate arguments in ARGS and build a single
+   inferior argument string.  The string returned by this function will be
+   equivalent, but not necessarily identical to the string passed to
+   ::split, for example passing the string '"a b"' (without the single
+   quotes, but including the double quotes) to ::split, will return an
+   argument of 'a b' (without the single quotes).  When this argument is
+   passed through ::join we will get back the string 'a\ b' (without the
+   single quotes), that is, we choose to escape the white space, rather
+   than wrap the argument in quotes.  */
+extern std::string join (const std::vector<char *> &args);
+
+} /* namespace remote_args */
+
+} /* namespac gdb */
