diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.c b/gdb/testsuite/gdb.arch/aarch64-gcs.c
new file mode 100644
index 000000000000..8e579de10cdb
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-gcs.c
@@ -0,0 +1,168 @@
+/* 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 <signal.h>
+#include <sys/auxv.h>
+#include <sys/syscall.h>
+#include <linux/prctl.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;								\
+  })
+
+static unsigned long *handler_gcspr = 0;
+
+static void
+handler (int sig)
+{
+  handler_gcspr = get_gcspr ();
+}
+
+static int __attribute__ ((unused))
+called_from_gdb (int val)
+{
+  return val + 1;
+}
+
+/* Corrupt the return address to see if GDB will report a SIGSEGV with the expected
+   $_siginfo.si_code.  */
+static void __attribute__ ((noinline))
+normal_function2 (void)
+{
+  /* x30 holds the return address.  */
+  register unsigned long x30 __asm__("x30") __attribute__ ((unused));
+
+  /* Cause a GCS exception.  */
+  x30 = 0xbadc0ffee;
+  __asm__ volatile("ret\n");
+}
+
+static inline void __attribute__ ((__always_inline__))
+inline_function2 (void)
+{
+  normal_function2 ();
+}
+
+/* Corrupt the return address to see if GDB will report a GCS error in this
+   function's frame .  */
+static void __attribute__ ((noinline))
+normal_function1 (void)
+{
+  /* x30 holds the return address.  */
+  register unsigned long x30 __asm__ ("x30") __attribute__ ((unused));
+  x30 = 0xbadc0ffee;
+  inline_function2 ();
+}
+
+static inline void __attribute__ ((__always_inline__))
+inline_function1 (void)
+{
+  normal_function1 ();
+}
+
+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;
+	}
+    }
+
+  /* This is used by GDB.  */
+  __attribute__((unused)) unsigned long *gcspr = get_gcspr ();
+
+  struct sigaction act = { 0 };
+
+  act.sa_handler = &handler;	/* Break here.  */
+  if (sigaction (SIGUSR1, &act, NULL) == -1)
+    {
+      perror ("sigaction");
+      exit (EXIT_FAILURE);
+    }
+
+  raise (SIGUSR1);
+
+  inline_function1 ();
+
+  /* Avoid returning, in case libc doesn't understand GCS.  */
+  exit (EXIT_SUCCESS);
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.exp b/gdb/testsuite/gdb.arch/aarch64-gcs.exp
new file mode 100644
index 000000000000..211fabf2f86b
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-gcs.exp
@@ -0,0 +1,78 @@
+# 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 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
+}
+
+gdb_test "print \$gcs_features_enabled" \
+    [string_to_regexp { = [ PR_SHADOW_STACK_ENABLE ]}] \
+    "GCS is enabled"
+
+gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "GDB knows about gcspr"
+gdb_test "print \$gcspr == gcspr" ". = 1" "GDB has the correct gcspr value"
+gdb_test_no_output "set \$gcspr_in_main = \$gcspr" \
+    "save gcspr value in main for later"
+
+# If the inferior function call fails, we don't want the tests following it
+# to be affected.
+gdb_test_no_output "set unwindonsignal on"
+gdb_test "print called_from_gdb (41)" ". = 42" "call inferior function"
+
+gdb_test "break handler" "Breakpoint \[0-9\]+ .*aarch64-gcs.c, line \[0-9\]+\\."
+gdb_test "handle SIGUSR1 nostop" \
+    ".*\r\nSIGUSR1\\s+No\\s+Yes\\s+Yes\\s+User defined signal 1" \
+    "let the inferior receive SIGUSR1 uninterrupted"
+gdb_test "continue" \
+    ".*\r\nBreakpoint \[0-9\]+, handler \\(sig=10\\) at .*aarch64-gcs.c.*handler_gcspr = get_gcspr \\(\\);" \
+    "continue to signal handler"
+
+gdb_test_no_output "set \$gcspr_in_handler = \$gcspr" \
+    "save gcspr value in handler for later"
+# Select the frame above the <signal handler called> frame, which makes GDB
+# unwind the gcspr from the signal frame GCS context.
+gdb_test "frame 2" "#2  ($hex in )?\\S+ \\(.*\\) (at|from) \\S+.*" \
+    "reached frame 2"
+gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "gcspr in frame level 2"
+gdb_test "print \$gcspr == \$gcspr_in_handler + 8" ". = 1" \
+    "gcspr unwound from signal context is correct"
+
+gdb_test "continue" \
+    [multi_line \
+	 "Continuing\\." \
+	 "" \
+	 "Program received signal SIGSEGV, Segmentation fault" \
+	 "Guarded Control Stack error\\." \
+	 "normal_function2 \\(\\) at .*aarch64-gcs.c:$decimal" \
+	 "${decimal}\\s+__asm__ volatile\\(\"ret\\\\n\"\\);"] \
+    "continue to SIGSEGV"
+
+gdb_test "print \$_siginfo.si_code" ". = 10" \
+    "test value of si_code when GCS SIGSEGV happens"
+# The GCS grows down, and there are two real frames until main.
+gdb_test "print \$gcspr == \$gcspr_in_main - 16" ". = 1" \
+    "test value of gcspr when GCS SIGSEGV happens"
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index ea498c430405..2adfd4b1bcfe 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -5094,6 +5094,64 @@ gdb_caching_proc allow_aarch64_mops_tests {} {
     return $allow_mops_tests
 }
 
+# Run a test on the target to see if it supports Aarch64 GCS extensions.
+# Return 0 if so, 1 if it does not.  Note this causes a restart of GDB.
+
+gdb_caching_proc allow_aarch64_gcs_tests {} {
+    global srcdir subdir gdb_prompt inferior_exited_re
+
+    set me "allow_aarch64_gcs_tests"
+
+    if { ![is_aarch64_target]} {
+	return 0
+    }
+
+    # Compile a program that tests the GCS feature.
+    set src {
+	#include <stdbool.h>
+	#include <sys/auxv.h>
+
+	/* Feature check for Guarded Control Stack.  */
+	#ifndef HWCAP_GCS
+	#define HWCAP_GCS (1UL << 32)
+	#endif
+
+	int main (void) {
+	    bool gcs_supported = getauxval (AT_HWCAP) & HWCAP_GCS;
+
+	    /* Return success if GCS is supported.  */
+	    return !gcs_supported;
+	}
+    }
+
+    if {![gdb_simple_compile $me $src executable]} {
+	return 0
+    }
+
+    # Compilation succeeded so now run it via gdb.
+    clean_restart $obj
+    gdb_run_cmd
+    gdb_expect {
+	-re ".*$inferior_exited_re with code 01.*${gdb_prompt} $" {
+	    verbose -log "\n$me gcs support not detected"
+	    set allow_gcs_tests 0
+	}
+	-re ".*$inferior_exited_re normally.*${gdb_prompt} $" {
+	    verbose -log "\n$me: gcs support detected"
+	    set allow_gcs_tests 1
+	}
+	default {
+	  warning "\n$me: default case taken"
+	    set allow_gcs_tests 0
+	}
+    }
+    gdb_exit
+    remote_file build delete $obj
+
+    verbose "$me:  returning $allow_gcs_tests" 2
+    return $allow_gcs_tests
+}
+
 # A helper that compiles a test case to see if __int128 is supported.
 proc gdb_int128_helper {lang} {
     return [gdb_can_simple_compile "i128-for-$lang" {
