diff --git a/gdb/NEWS b/gdb/NEWS
index cc262f1f8a6..65ac722bd2e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -91,6 +91,14 @@ show always-read-ctf
    $2 = 1
    (gdb) break func if $_shell("some command") == 0
 
+* Changed commands
+
+gcore
+generate-core-file
+  For non-stop targets, GDB ensures that all threads are stopped before
+  generating a core dump.  At the end of the command, threads are restored
+  to their previous state.
+
 * MI changes
 
 ** mi now reports 'no-history' as a stop reason when hitting the end of the
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 6c811b8be2e..574684d9e28 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -13763,6 +13763,11 @@ Produce a core dump of the inferior process.  The optional argument
 specified, the file name defaults to @file{core.@var{pid}}, where
 @var{pid} is the inferior process ID.
 
+If the current inferior's target is non-stop, @value{GDBN} ensures that
+all the inferior's threads are stopped while generating the core dump.
+@value{GDBN} stops every running threads of the inferior before
+generating the core dump and resumes them when the command is done.
+
 Note that this command is implemented only for some systems (as of
 this writing, @sc{gnu}/Linux, FreeBSD, Solaris, and S390).
 
diff --git a/gdb/gcore.c b/gdb/gcore.c
index 973abadb013..217976ac4e0 100644
--- a/gdb/gcore.c
+++ b/gdb/gcore.c
@@ -34,6 +34,7 @@
 #include "regset.h"
 #include "gdb_bfd.h"
 #include "readline/tilde.h"
+#include "infrun.h"
 #include <algorithm>
 #include "gdbsupport/gdb_unlinker.h"
 #include "gdbsupport/byte-vector.h"
@@ -131,6 +132,20 @@ gcore_command (const char *args, int from_tty)
   if (!target_has_execution ())
     noprocess ();
 
+  scoped_restore_current_thread restore_current_thread;
+  scoped_disable_commit_resumed disable_commit_resume ("generating coredump");
+  struct inferior *inf = current_inferior ();
+  scoped_finish_thread_state finish_state (inf->process_target (),
+					   ptid_t (inf->pid));
+
+  if (target_is_non_stop_p (inf))
+    stop_all_threads ("generating coredump", inf);
+  else
+    for (thread_info *thread : inf->non_exited_threads ())
+      if (thread->executing ())
+	error (_("At least one thread is still running, cannot generate a "
+		 "core dump."));
+
   if (args && *args)
     corefilename.reset (tilde_expand (args));
   else
@@ -161,6 +176,11 @@ gcore_command (const char *args, int from_tty)
     }
 
   gdb_printf ("Saved corefile %s\n", corefilename.get ());
+
+  if (exists_non_stop_target ())
+    restart_threads (nullptr, inf);
+
+  disable_commit_resume.reset_and_commit ();
 }
 
 static enum bfd_architecture
diff --git a/gdb/infrun.c b/gdb/infrun.c
index a509e0d5bef..7364bfa623c 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -97,9 +97,6 @@ static void resume (gdb_signal sig);
 
 static void wait_for_inferior (inferior *inf);
 
-static void restart_threads (struct thread_info *event_thread,
-			     inferior *inf = nullptr);
-
 static bool start_step_over (void);
 
 static bool step_over_info_valid_p (void);
@@ -6008,18 +6005,15 @@ handle_inferior_event (struct execution_control_state *ecs)
     }
 }
 
-/* Restart threads back to what they were trying to do back when we
-   paused them (because of an in-line step-over or vfork, for example).
-   The EVENT_THREAD thread is ignored (not restarted).
-
-   If INF is non-nullptr, only resume threads from INF.  */
+/* See infrun.h.  */
 
-static void
+void
 restart_threads (struct thread_info *event_thread, inferior *inf)
 {
   INFRUN_SCOPED_DEBUG_START_END ("event_thread=%s, inf=%d",
-				 event_thread->ptid.to_string ().c_str (),
-				 inf != nullptr ? inf->num : -1);
+				 (event_thread != nullptr
+				  ? event_thread->ptid.to_string ().c_str ()
+				  : "None"), inf != nullptr ? inf->num : -1);
 
   gdb_assert (!step_over_info_valid_p ());
 
diff --git a/gdb/infrun.h b/gdb/infrun.h
index 5219063586d..4b2a04e503b 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -182,6 +182,15 @@ extern void nullify_last_target_wait_ptid ();
    all threads of all inferiors.  */
 extern void stop_all_threads (const char *reason, inferior *inf = nullptr);
 
+/* Restart threads back to what they were trying to do back when we
+   paused them (because of an in-line step-over or vfork, for example).
+   The EVENT_THREAD thread, if non-nullptr, is ignored (not restarted).
+
+   If INF is non-nullptr, only resume threads from INF.  */
+
+extern void restart_threads (struct thread_info *event_thread,
+			     inferior *inf = nullptr);
+
 extern void prepare_for_detach (void);
 
 extern void fetch_inferior_event ();
diff --git a/gdb/testsuite/gdb.base/gcore-nonstop.c b/gdb/testsuite/gdb.base/gcore-nonstop.c
new file mode 100644
index 00000000000..a96c011ad14
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gcore-nonstop.c
@@ -0,0 +1,44 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-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 <pthread.h>
+
+static pthread_barrier_t barrier;
+
+static void *
+worker_func (void *ignored)
+{
+  pthread_barrier_wait (&barrier);
+  return NULL;
+}
+
+int
+main (void)
+{
+  pthread_t worker_thread;
+  pthread_barrier_init (&barrier, NULL, 2);
+
+  pthread_create (&worker_thread, NULL, worker_func, NULL);
+
+  /* Break here.  */
+
+  pthread_barrier_wait (&barrier);
+  pthread_join (worker_thread, NULL);
+  pthread_barrier_destroy (&barrier);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/gcore-nonstop.exp b/gdb/testsuite/gdb.base/gcore-nonstop.exp
new file mode 100644
index 00000000000..172dd760e73
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gcore-nonstop.exp
@@ -0,0 +1,79 @@
+# Copyright 2022-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/>.
+
+# Check that in non-stop mode, if some threads are running when the user
+# launches the "gcore" command, GDB suspends all threads, generates the core
+# file and resumes threads which where running before the "gcore" command
+# got issued.  If running threads were not stopped, GDB would report errors
+# when trying to capture the thread's state.
+
+standard_testfile
+
+if { [build_executable "failed to build" \
+	${testfile} ${srcfile} {pthreads debug}] } {
+    return
+}
+
+save_vars { GDBFLAGS } {
+    append GDBFLAGS " -ex \"set non-stop on\""
+    clean_restart ${binfile}
+}
+
+set lineno [gdb_get_line_number "Break here"]
+if { ![runto $lineno] } {
+    return
+}
+
+# We should be stopped in thread 1 while thread 2 is running.
+gdb_test_sequence "info threads" "info threads" {
+    {Id\s+Target Id\s+Frame}
+    {\*\s+1[^\n]*\n}
+    {\s+2\s+[^\n]*\(running\)[^\n]*\n}
+}
+
+set th1_pc ""
+gdb_test_multiple "p/x \$pc" "fetch thread 1 PC" {
+  -wrap -re "$::decimal = ($::hex)" {
+    set th1_pc $expect_out(1,string)
+    pass $gdb_test_name
+  }
+}
+
+set corefile [standard_output_file "corefile"]
+if {![gdb_gcore_cmd $corefile "generate corefile"]} {
+  # gdb_gcore_cmd issues a "UNSUPPORTED".
+  return
+}
+
+# After the core file is generated, thread 2 should be back running
+# and thread 1 should still be selected.
+gdb_test_sequence "info threads" "correct thread selection after gcore" {
+    {Id\s+Target Id\s+Frame}
+    {\*\s+1[^\n]*\n}
+    {\s+2\s+[^\n]*\(running\)[^\n]*\n}
+}
+
+# Thread 1 is at the same PC it was before calling the gcore command.
+gdb_test "p/x \$pc" "\\\$$::decimal = $th1_pc" "thread 1 unchanged"
+
+clean_restart $binfile
+gdb_test "core-file $corefile" "Core was generated by.*" "load corefile"
+
+# The core file has the 2 threads.
+gdb_test_sequence "info threads" "threads in corefile" {
+    {Id\s+Target Id\s+Frame}
+    {\s+1\s+Thread[^\n]*\n}
+    {\s+2\s+Thread[^\n]*\n}
+}
