diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.c b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c
new file mode 100644
index 000000000000..d04bd76e0799
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c
@@ -0,0 +1,124 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2025 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>
+#include <stdlib.h>
+#include <sys/auxv.h>
+#include <linux/prctl.h>
+#include <sys/syscall.h>
+
+/* Feature check for Guarded Control Stack.  */
+#ifndef HWCAP_GCS
+#define HWCAP_GCS (1UL << 32)
+#endif
+
+#ifndef PR_GET_SHADOW_STACK_STATUS
+#define PR_GET_SHADOW_STACK_STATUS 74
+#define PR_SET_SHADOW_STACK_STATUS 75
+#define PR_SHADOW_STACK_ENABLE (1UL << 0)
+#endif
+
+/* We need to use a macro to call prctl because after GCS is enabled, it's not
+   possible to return from the function which enabled it.  This is because the
+   return address of the calling function isn't on the GCS.  */
+#define my_syscall2(num, arg1, arg2)					\
+  ({									\
+    register long _num __asm__("x8") = (num);				\
+    register long _arg1 __asm__("x0") = (long)(arg1);			\
+    register long _arg2 __asm__("x1") = (long)(arg2);			\
+    register long _arg3 __asm__("x2") = 0;				\
+    register long _arg4 __asm__("x3") = 0;				\
+    register long _arg5 __asm__("x4") = 0;				\
+									\
+    __asm__ volatile("svc #0\n"						\
+		     : "=r"(_arg1)					\
+		     : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4),	\
+		       "r"(_arg5), "r"(_num)				\
+		     : "memory", "cc");					\
+    _arg1;								\
+  })
+
+#define get_gcspr(void)							\
+  ({									\
+    unsigned long *gcspr;						\
+									\
+    /* Get GCSPR_EL0.  */						\
+    asm volatile("mrs	%0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc");	\
+									\
+    gcspr;								\
+  })
+
+/* Corrupt the return address to see if GDB will report a SIGSEGV with the
+   expected
+   $_siginfo.si_code.  */
+static void __attribute__ ((noinline))
+function (unsigned long *gcspr)
+{
+  /* x30 holds the return address.  */
+  register long x30 __asm__("x30") __attribute__ ((unused));
+
+  /* Print GCSPR to stdout so that the testcase can capture it.  */
+  printf ("%p\n", get_gcspr ());
+  fflush (stdout);
+
+  /* Cause a GCS exception.  */
+  x30 = 0xbadc0ffee;
+  __asm__ volatile("ret\n");
+}
+
+int
+main (void)
+{
+  if (!(getauxval (AT_HWCAP) & HWCAP_GCS))
+    {
+      fprintf (stderr, "GCS support not found in AT_HWCAP\n");
+      return EXIT_FAILURE;
+    }
+
+  /* Force shadow stacks on, our tests *should* be fine with or
+     without libc support and with or without this having ended
+     up tagged for GCS and enabled by the dynamic linker.  We
+     can't use the libc prctl() function since we can't return
+     from enabling the stack.  Also lock GCS if not already
+     locked so we can test behaviour when it's locked.  */
+  unsigned long gcs_mode;
+  int ret = my_syscall2 (__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode);
+  if (ret)
+    {
+      fprintf (stderr, "Failed to read GCS state: %d\n", ret);
+      return EXIT_FAILURE;
+    }
+
+  if (!(gcs_mode & PR_SHADOW_STACK_ENABLE))
+    {
+      gcs_mode = PR_SHADOW_STACK_ENABLE;
+      ret = my_syscall2 (__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode);
+      if (ret)
+	{
+	  fprintf (stderr, "Failed to configure GCS: %d\n", ret);
+	  return EXIT_FAILURE;
+	}
+    }
+
+  unsigned long *gcspr = get_gcspr ();
+
+  /* Pass gscpr to function just so it's used for something.  */
+  function (gcspr);        /* Break here.  */
+
+  /* Avoid returning, in case libc doesn't understand GCS.  */
+  exit (EXIT_SUCCESS);
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp
new file mode 100644
index 000000000000..17bbb5e4ded9
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp
@@ -0,0 +1,105 @@
+# Copyright 2025 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 reading and writing the core dump of a binary that uses a Guarded
+# Control Stack.
+
+require allow_aarch64_gcs_tests
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+    return
+}
+
+set linespec ${srcfile}:[gdb_get_line_number "Break here"]
+
+if ![runto $linespec] {
+    return
+}
+
+# Continue until a crash.  The line with the hex number is optional because
+# it's printed by the test program, and doesn't appear in the Expect buffer
+# when testing a remote target.
+gdb_test "continue" \
+    [multi_line \
+	 "Continuing\\." \
+	 "($hex\r\n)?" \
+	 "Program received signal SIGSEGV, Segmentation fault" \
+	 "Guarded Control Stack error\\." \
+	 "function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \
+	 {.*__asm__ volatile\("ret\\n"\);}] \
+    "continue to SIGSEGV"
+
+set gcspr_in_gcore [get_valueof "/x" "\$gcspr" "*unknown*"]
+
+# Generate the gcore core file.
+set gcore_filename [standard_output_file "${testfile}.gcore"]
+set gcore_generated [gdb_gcore_cmd "$gcore_filename" "generate gcore file"]
+
+# Obtain an OS-generated core file.
+set core_filename [core_find $binfile {} {} "${binfile}.out"]
+set core_generated [expr {$core_filename != ""}]
+set os_core_name "${binfile}.core"
+remote_exec build "mv $core_filename $os_core_name"
+set core_filename $os_core_name
+
+# At this point we have a couple of core files, the gcore one generated by
+# GDB and the one generated by the operating system.  Make sure GDB can
+# read both correctly.
+
+proc check_core_file {core_filename saved_gcspr} {
+    global decimal hex
+
+    # Load the core file.
+    if [gdb_test "core $core_filename" \
+	    [multi_line \
+		 "Core was generated by .*\\." \
+		 "Program terminated with signal SIGSEGV, Segmentation fault" \
+		 "Guarded Control Stack error\\." \
+		 "#0  function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \
+		 "$decimal.*__asm__ volatile\\(\"ret\\\\n\"\\);"] \
+	    "load core file"] {
+	return -1
+    }
+
+    # Check the value of GCSPR in the core file.
+    gdb_test "print/x \$gcspr" "\\$\[0-9\]+ = $saved_gcspr" \
+	"gcspr contents from core file"
+}
+
+if {$gcore_generated} {
+    clean_restart $binfile
+
+    with_test_prefix "gcore corefile" {
+	check_core_file $gcore_filename $gcspr_in_gcore
+    }
+} else {
+    fail "gcore corefile not generated"
+}
+
+if {$core_generated} {
+    clean_restart $binfile
+
+    with_test_prefix "OS corefile" {
+	set out_id [open ${binfile}.out "r"]
+	set gcspr_in_core [gets $out_id]
+
+	close $out_id
+	check_core_file $core_filename $gcspr_in_core
+    }
+} else {
+    untested "OS corefile not generated"
+}
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 2adfd4b1bcfe..cf0b7cb9562d 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -9408,7 +9408,7 @@ proc remove_core {pid {test ""}} {
     }
 }
 
-proc core_find {binfile {deletefiles {}} {arg ""}} {
+proc core_find {binfile {deletefiles {}} {arg ""} {output_file "/dev/null"}} {
     global objdir subdir
 
     set destcore "$binfile.core"
@@ -9430,7 +9430,7 @@ proc core_find {binfile {deletefiles {}} {arg ""}} {
     set found 0
     set coredir [standard_output_file coredir.[getpid]]
     file mkdir $coredir
-    catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >/dev/null 2>&1\""
+    catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >${output_file} 2>&1\""
     #      remote_exec host "${binfile}"
     foreach i "${coredir}/core ${coredir}/core.coremaker.c ${binfile}.core" {
 	if [remote_file build exists $i] {
