diff mbox series

[3/8] GDB, gdbserver: aarch64-linux: Initial Guarded Control Stack support

Message ID 20250608010338.2234530-4-thiago.bauermann@linaro.org
State New
Headers show
Series AArch64 Guarded Control Stack support | expand

Commit Message

Thiago Jung Bauermann June 8, 2025, 1:03 a.m. UTC
Add the org.gnu.gdb.aarch64.gcs feature with the GCSPR register, and the
org.gnu.gdb.aarch64.gcs.linux feature with "registers" to represent the
Linux kernel ptrace and prctl knobs that enable and lock specific GCS
functionality.
---
 gdb/aarch64-linux-nat.c            |  79 ++++++++++++++++++++++
 gdb/aarch64-linux-tdep.c           |  23 +++++++
 gdb/aarch64-tdep.c                 | 101 +++++++++++++++++++++++++++++
 gdb/aarch64-tdep.h                 |  14 ++++
 gdb/arch/aarch64-gcs-linux.h       |  44 +++++++++++++
 gdb/arch/aarch64.c                 |   8 +++
 gdb/arch/aarch64.h                 |  10 ++-
 gdb/doc/gdb.texinfo                |  51 +++++++++++++++
 gdb/features/Makefile              |   2 +
 gdb/features/aarch64-gcs-linux.c   |  21 ++++++
 gdb/features/aarch64-gcs-linux.xml |  18 +++++
 gdb/features/aarch64-gcs.c         |  14 ++++
 gdb/features/aarch64-gcs.xml       |  11 ++++
 gdbserver/linux-aarch64-low.cc     |  46 +++++++++++++
 14 files changed, 441 insertions(+), 1 deletion(-)
 create mode 100644 gdb/arch/aarch64-gcs-linux.h
 create mode 100644 gdb/features/aarch64-gcs-linux.c
 create mode 100644 gdb/features/aarch64-gcs-linux.xml
 create mode 100644 gdb/features/aarch64-gcs.c
 create mode 100644 gdb/features/aarch64-gcs.xml

Comments

Schimpe, Christina June 11, 2025, 10:06 a.m. UTC | #1
Hi Thiago, 

Please see my comments below.

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Sunday, June 8, 2025 3:03 AM
> To: gdb-patches@sourceware.org
> Subject: [PATCH 3/8] GDB, gdbserver: aarch64-linux: Initial Guarded Control Stack
> support
> 
> Add the org.gnu.gdb.aarch64.gcs feature with the GCSPR register, and the
> org.gnu.gdb.aarch64.gcs.linux feature with "registers" to represent the Linux
> kernel ptrace and prctl knobs that enable and lock specific GCS functionality.

You only add the el0 register? Then you could mention this explicitly in the commit message I think.
Or is there only userspace support for GCS ?

> ---
>  gdb/aarch64-linux-nat.c            |  79 ++++++++++++++++++++++
>  gdb/aarch64-linux-tdep.c           |  23 +++++++
>  gdb/aarch64-tdep.c                 | 101 +++++++++++++++++++++++++++++
>  gdb/aarch64-tdep.h                 |  14 ++++
>  gdb/arch/aarch64-gcs-linux.h       |  44 +++++++++++++
>  gdb/arch/aarch64.c                 |   8 +++
>  gdb/arch/aarch64.h                 |  10 ++-
>  gdb/doc/gdb.texinfo                |  51 +++++++++++++++
>  gdb/features/Makefile              |   2 +
>  gdb/features/aarch64-gcs-linux.c   |  21 ++++++
>  gdb/features/aarch64-gcs-linux.xml |  18 +++++
>  gdb/features/aarch64-gcs.c         |  14 ++++
>  gdb/features/aarch64-gcs.xml       |  11 ++++
>  gdbserver/linux-aarch64-low.cc     |  46 +++++++++++++	
>  14 files changed, 441 insertions(+), 1 deletion(-)  create mode 100644
> gdb/arch/aarch64-gcs-linux.h  create mode 100644 gdb/features/aarch64-gcs-
> linux.c  create mode 100644 gdb/features/aarch64-gcs-linux.xml
>  create mode 100644 gdb/features/aarch64-gcs.c  create mode 100644
> gdb/features/aarch64-gcs.xml
> 
> diff --git a/gdb/aarch64-linux-nat.c b/gdb/aarch64-linux-nat.c index
> d7869f42e825..0796b1e47955 100644
> --- a/gdb/aarch64-linux-nat.c
> +++ b/gdb/aarch64-linux-nat.c
> @@ -51,6 +51,7 @@
>  #include "gdb_proc_service.h"
>  #include "arch-utils.h"
> 
> +#include "arch/aarch64-gcs-linux.h"
>  #include "arch/aarch64-mte-linux.h"
> 
>  #include "nat/aarch64-mte-linux-ptrace.h"
> @@ -542,6 +543,67 @@ store_tlsregs_to_thread (struct regcache *regcache)
>      perror_with_name (_("unable to store TLS register"));  }
> 
> +/* Fill GDB's register array with the GCS register values from
> +   the current thread.  */
> +
> +static void
> +fetch_gcsregs_from_thread (struct regcache *regcache) {
> +  aarch64_gdbarch_tdep *tdep
> +    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
>

Nit: is the "struct" keyword necessary ? 
struct regcache *regcache => regcache *regcache

> +  gdb_assert (tdep->gcs_reg_base != -1);  gdb_assert
> + (tdep->gcs_linux_reg_base != -1);
> +
> +  struct user_gcs user_gcs;
> +  struct iovec iovec;
> 

Also here:
struct user_gcs user_gcs; => user_gcs user_gcs;
struct iovec iovec; => iovec iovec;

There are some more occurrences in this patch, but I'll stop here
to comment on each of them.

> +  iovec.iov_base = &user_gcs;
> +  iovec.iov_len = sizeof (user_gcs);
> +
> +  int tid = get_ptrace_pid (regcache->ptid ());  if (ptrace
> + (PTRACE_GETREGSET, tid, NT_ARM_GCS, &iovec) != 0)
> +      perror_with_name (_("unable to fetch GCS registers"));

Nit: I think the ident is only 2 spaces?

> +  regcache->raw_supply (tdep->gcs_reg_base, &user_gcs.gcspr_el0);
> +  regcache->raw_supply (tdep->gcs_linux_reg_base,
> +&user_gcs.features_enabled);
> +  regcache->raw_supply (tdep->gcs_linux_reg_base + 1,
> +			&user_gcs.features_locked);
> +}
> +
> +/* Store to the current thread the valid GCS register set in the GDB's
> +   register array.  */
> +
> +static void
> +store_gcsregs_to_thread (struct regcache *regcache) {
> +  aarch64_gdbarch_tdep *tdep
> +    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
> +
> +  gdb_assert (tdep->gcs_reg_base != -1);  gdb_assert
> + (tdep->gcs_linux_reg_base != -1);
> +
> +  if (REG_VALID != regcache->get_register_status (tdep->gcs_reg_base)
> +      || REG_VALID != regcache->get_register_status (tdep->gcs_linux_reg_base)
> +      || REG_VALID
> +	     != regcache->get_register_status (tdep->gcs_linux_reg_base + 1))
> +    return;

Nit: I think the ident should only be 2 spaces, after the level of REG_VALID.

> +  struct user_gcs user_gcs;
> +  regcache->raw_collect (tdep->gcs_reg_base, &user_gcs.gcspr_el0);
> +  regcache->raw_collect (tdep->gcs_linux_reg_base,
> +&user_gcs.features_enabled);
> +  regcache->raw_collect (tdep->gcs_linux_reg_base + 1,
> +			 &user_gcs.features_locked);
> +
> +  struct iovec iovec;
> +  iovec.iov_base = &user_gcs;
> +  iovec.iov_len = sizeof (user_gcs);
> +
> +  int tid = get_ptrace_pid (regcache->ptid ());
> +  if (ptrace (PTRACE_SETREGSET, tid, NT_ARM_GCS, &iovec) != 0)
> +    perror_with_name (_("unable to store GCS registers")); }
> +
>  /* The AArch64 version of the "fetch_registers" target_ops method.  Fetch
>     REGNO from the target and place the result into REGCACHE.  */
> 
> @@ -577,6 +639,9 @@ aarch64_fetch_registers (struct regcache *regcache, int
> regno)
> 
>        if (tdep->has_sme2 ())
>  	fetch_zt_from_thread (regcache);
> +
> +      if (tdep->has_gcs ())
> +	fetch_gcsregs_from_thread (regcache);

So you call the method fetch_gcsregs_from_thread which lives inside a *-linux-* file and also uses
"registers" of the org.gnu.gdb.aarch64.gcs.linux feature.  Shouldn't has_gcs () also check for the
linux specific register gcs_linux_reg_base or am I missing something?

What happens if you try to debug on a machine without linux kernel GCS support but support in HW?

>      }
>    /* General purpose register?  */
>    else if (regno < AARCH64_V0_REGNUM)
> @@ -609,6 +674,11 @@ aarch64_fetch_registers (struct regcache *regcache, int
> regno)
>  	   && regno >= tdep->tls_regnum_base
>  	   && regno < tdep->tls_regnum_base + tdep->tls_register_count)
>      fetch_tlsregs_from_thread (regcache);
> +  /* GCS register?  */
> +  else if (tdep->has_gcs ()
> +	   && (regno == tdep->gcs_reg_base || regno == tdep-
> >gcs_linux_reg_base
> +	       || regno == tdep->gcs_linux_reg_base + 1))
> +    fetch_gcsregs_from_thread (regcache);
>  }
> 
>  /* A version of the "fetch_registers" target_ops method used when running @@
> -680,6 +750,9 @@ aarch64_store_registers (struct regcache *regcache, int
> regno)
> 
>        if (tdep->has_sme2 ())
>  	store_zt_to_thread (regcache);
> +
> +      if (tdep->has_gcs ())
> +	store_gcsregs_to_thread (regcache);

Similar comment for the linux specific "registers".

>      }
>    /* General purpose register?  */
>    else if (regno < AARCH64_V0_REGNUM)
> @@ -706,6 +779,11 @@ aarch64_store_registers (struct regcache *regcache, int
> regno)
>  	   && regno >= tdep->tls_regnum_base
>  	   && regno < tdep->tls_regnum_base + tdep->tls_register_count)
>      store_tlsregs_to_thread (regcache);
> +  /* GCS register?  */
> +  else if (tdep->has_gcs ()
> +	   && (regno == tdep->gcs_reg_base || regno == tdep-
> >gcs_linux_reg_base
> +	       || regno == tdep->gcs_linux_reg_base + 1))
> +    store_gcsregs_to_thread (regcache);
> 
>    /* PAuth registers are read-only.  */  } @@ -881,6 +959,7 @@
> aarch64_linux_nat_target::read_description ()
>       active or not.  */
>    features.vq = aarch64_sve_get_vq (tid);
>    features.pauth = hwcap & AARCH64_HWCAP_PACA;
> +  features.gcs = features.gcs_linux = hwcap & HWCAP_GCS;
>    features.mte = hwcap2 & HWCAP2_MTE;
>    features.tls = aarch64_tls_register_count (tid);
>    /* SME feature check.  */
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c index
> a194ac809c23..ce213bb482b9 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -50,6 +50,7 @@
>  #include "record-full.h"
>  #include "linux-record.h"
> 
> +#include "arch/aarch64-gcs-linux.h"
>  #include "arch/aarch64-mte.h"
>  #include "arch/aarch64-mte-linux.h"
>  #include "arch/aarch64-scalable-linux.h"
> @@ -1604,6 +1605,27 @@ aarch64_linux_iterate_over_regset_sections (struct
> gdbarch *gdbarch,
>        cb (".reg-aarch-tls", sizeof_tls_regset, sizeof_tls_regset,
>  	  &aarch64_linux_tls_regset, "TLS register", cb_data);
>      }
> +
> +  /* Handle GCS registers.  */
> +  if (tdep->has_gcs ())
> +    {
> +      /* Create this on the fly in order to handle the variable regnums.  */
> +      const struct regcache_map_entry gcs_regmap[] =
> +	{
> +	  { 1, tdep->gcs_linux_reg_base, 8 },      /* features_enabled */
> +	  { 1, tdep->gcs_linux_reg_base + 1, 8 },  /* features_locked */
> +	  { 1, tdep->gcs_reg_base, 8 },            /* GCSPR */
> +	  { 0 }
> +	};
> +
> +      const struct regset aarch64_linux_gcs_regset =
> +	{
> +	  gcs_regmap, regcache_supply_regset, regcache_collect_regset
> +	};
> +
> +      cb (".reg-aarch-gcs", sizeof (struct user_gcs), sizeof (struct user_gcs),
> +	  &aarch64_linux_gcs_regset, "GCS registers", cb_data);
> +    }
>  }
> 
>  /* Implement the "core_read_description" gdbarch method.  */ @@ -1628,6
> +1650,7 @@ aarch64_linux_core_read_description (struct gdbarch *gdbarch,
>       length.  */
>    features.vq = aarch64_linux_core_read_vq_from_sections (gdbarch, abfd);
>    features.pauth = hwcap & AARCH64_HWCAP_PACA;
> +  features.gcs = features.gcs_linux = hwcap & HWCAP_GCS;
>    features.mte = hwcap2 & HWCAP2_MTE;
> 
>    /* Handle the TLS section.  */
> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> 8d54e59f332a..d728f60e9e15 100644
> --- a/gdb/aarch64-tdep.c
> +++ b/gdb/aarch64-tdep.c
> @@ -159,6 +159,18 @@ static const char *const aarch64_mte_register_names[]
> =
>    "tag_ctl"
>  };
> 
> +static const char *const aarch64_gcs_register_names[] = {
> +  /* Guarded Control Stack Pointer Register.  */
> +  "gcspr"
> +};
> +
> +static const char *const aarch64_gcs_linux_register_names[] = {
> +  /* Field in struct user_gcs.  */
> +  "gcs_features_enabled",
> +  /* Field in struct user_gcs.  */
> +  "gcs_features_locked",
> +};
> +
>  static int aarch64_stack_frame_destroyed_p (struct gdbarch *, CORE_ADDR);
> 
>  /* AArch64 prologue cache structure.  */ @@ -1875,6 +1887,39 @@
> pass_in_v_vfp_candidate (struct gdbarch *gdbarch, struct regcache *regcache,
>      }
>  }
> 
> +/* Push LR_VALUE to the Guarded Control Stack.  */
> +
> +static void
> +aarch64_push_gcs_entry (regcache *regs, CORE_ADDR lr_value) {
> +  gdbarch *arch = regs->arch ();
> +  aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep>
> +(arch);
> +  CORE_ADDR gcs_addr;
> +
> +  enum register_status status = regs->cooked_read (tdep->gcs_reg_base,
> +						   &gcs_addr);
> +  if (status != REG_VALID)
> +    error ("Can't read $gcspr.");

Nit: Shoudn't this better be:
 error (_("Can't read $gcspr."));, 
to make it i18n friendly?

> +  gcs_addr -= 8;
> +  gdb_byte buf[8];
> +  store_integer (buf, gdbarch_byte_order (arch), lr_value);  if
> + (target_write_memory (gcs_addr, buf, sizeof (buf)) != 0)
> +    error ("Can't write to Guarded Control Stack.");

Similar comment for i18n.

> +  /* Update GCSPR.  */
> +  regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr);
> +}
> +
> +/* Implement the "shadow_stack_push" gdbarch method.  */
> +
> +static void
> +aarch64_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr,
> +			   regcache *regcache)
> +{
> +  aarch64_push_gcs_entry (regcache, new_addr); }

Can't you simply pass gdbarch to aarch64_push_gcs_entry ?
Then there is no need for the additional call

gdbarch *arch = regs->arch ()

in aarch64_push_gcs_entry.

>  /* Implement the "push_dummy_call" gdbarch method.  */
> 
>  static CORE_ADDR
> @@ -4046,6 +4091,14 @@ aarch64_features_from_target_desc (const struct
> target_desc *tdesc)
>    features.sme2 = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.sme2")
>  		   != nullptr);
> 
> +  /* Check for the GCS feature.  */
> +  features.gcs = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs")
> +		  != nullptr);
> +
> +  /* Check for the GCS Linux feature.  */
> +  features.gcs_linux = (tdesc_find_feature (tdesc,
> "org.gnu.gdb.aarch64.gcs.linux")
> +			!= nullptr);
> +
>    return features;
>  }
> 
> @@ -4590,6 +4643,46 @@ aarch64_gdbarch_init (struct gdbarch_info info,
> struct gdbarch_list *arches)
>      int first_w_regnum = num_pseudo_regs;
>      num_pseudo_regs += 31;
> 
> +  const struct tdesc_feature *feature_gcs
> +      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs");
> +  int first_gcs_regnum = -1;
> +  /* Add the GCS registers.  */
> +  if (feature_gcs != nullptr)
> +    {
> +      first_gcs_regnum = num_regs;
> +      /* Validate the descriptor provides the mandatory GCS registers and
> +	 allocate their numbers.  */
> +      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_register_names); i++)
> +	valid_p &= tdesc_numbered_register (feature_gcs, tdesc_data.get (),
> +					    first_gcs_regnum + i,
> +					    aarch64_gcs_register_names[i]);
> +
> +      num_regs += i;
> +    }
> +
> +  if (!valid_p)
> +    return nullptr;
> +
> +  const struct tdesc_feature *feature_gcs_linux
> +      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux");
> +  int first_gcs_linux_regnum = -1;
> +  /* Add the GCS Linux registers.  */
> +  if (feature_gcs_linux != nullptr)
> +    {
> +      first_gcs_linux_regnum = num_regs;
> +      /* Validate the descriptor provides the mandatory GCS Linux registers
> +	 and allocate their numbers.  */
> +      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_linux_register_names); i++)
> +	valid_p &= tdesc_numbered_register (feature_gcs_linux, tdesc_data.get
> (),
> +					    first_gcs_linux_regnum + i,
> +
> aarch64_gcs_linux_register_names[i]);
> +
> +      /* This feature depends on the GCS feature.  */
> +      valid_p &= feature_gcs != nullptr;

Couldn't you check if feature_gcs != nullptr before

> +      /*  Validate the descriptor provides the mandatory GCS Linux registers
> +	 and allocate their numbers.  */ 
[...]

? Then you could return early.

> +      num_regs += i;
> +    }
> +
>    if (!valid_p)
>      return nullptr;
> 
> @@ -4611,6 +4704,8 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct
> gdbarch_list *arches)
>    tdep->mte_reg_base = first_mte_regnum;
>    tdep->tls_regnum_base = first_tls_regnum;
>    tdep->tls_register_count = tls_register_count;
> +  tdep->gcs_reg_base = first_gcs_regnum;  tdep->gcs_linux_reg_base =
> + first_gcs_linux_regnum;
> 
>    /* Set the SME register set details.  The pseudo-registers will be adjusted
>       later.  */
> @@ -4733,6 +4828,9 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct
> gdbarch_list *arches)
> 
>    set_gdbarch_get_pc_address_flags (gdbarch, aarch64_get_pc_address_flags);
> 
> +  if (tdep->has_gcs ())
> +    set_gdbarch_shadow_stack_push (gdbarch, aarch64_shadow_stack_push);
> +

Do I understand correctly, that you enable inferior calls by implementing this gdbarch method here?
Then I think it could be worth mentioning in the commit message I think.
Also I wonder - do you support the return command as well already?
Cause I couldn't find a specific test for it.

>    tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data));
> 
>    /* Fetch the updated number of registers after we're done adding all @@ -
> 4905,6 +5003,9 @@ aarch64_dump_tdep (struct gdbarch *gdbarch, struct ui_file
> *file)
>  	      pulongest (tdep->sme_tile_pseudo_base));
>    gdb_printf (file, _("aarch64_dump_tdep: sme_svq = %s\n"),
>  	      pulongest (tdep->sme_svq));
> +
> +  gdb_printf (file, _ ("aarch64_dump_tdep: gcs_reg_base = %d\n"),
> +	      tdep->gcs_reg_base);
>  }

Nit: no whitespace
gdb_printf (file, _ ("aarch64_dump_tdep: gcs_reg_base = %d\n")
=> gdb_printf (file, _("aarch64_dump_tdep: gcs_reg_base = %d\n")

>  #if GDB_SELF_TEST
> diff --git a/gdb/aarch64-tdep.h b/gdb/aarch64-tdep.h index
> 3b8dcc26545b..0332e80da7bc 100644
> --- a/gdb/aarch64-tdep.h
> +++ b/gdb/aarch64-tdep.h
> @@ -182,6 +182,20 @@ struct aarch64_gdbarch_tdep : gdbarch_tdep_base
>    {
>      return sme2_zt0_regnum > 0;
>    }
> +
> +  /* First GCS register.  This is -1 if no GCS registers are available.
> + */  int gcs_reg_base = -1;
> +
> +  /* First GCS Linux-specific register.  This is -1 if no GCS Linux feature is
> +     available.  */
> +  int gcs_linux_reg_base = -1;
> +
> +  /* Returns true if the target supports GCS.  */  bool  has_gcs ()
> + const  {
> +    return gcs_reg_base != -1;
> +  }
>  };
> 
>  const target_desc *aarch64_read_description (const aarch64_features
> &features); diff --git a/gdb/arch/aarch64-gcs-linux.h b/gdb/arch/aarch64-gcs-
> linux.h new file mode 100644 index 000000000000..9366caa7289a
> --- /dev/null
> +++ b/gdb/arch/aarch64-gcs-linux.h
> @@ -0,0 +1,44 @@
> +/* Common Linux target-dependent definitions for AArch64 GCS
> +
> +   Copyright (C) 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/>.  */
> +
> +#ifndef ARCH_AARCH64_GCS_LINUX_H
> +#define ARCH_AARCH64_GCS_LINUX_H
> +
> +#include <stdint.h>
> +
> +/* Feature check for Guarded Control Stack.  */ #ifndef HWCAP_GCS
> +#define HWCAP_GCS (1UL << 32) #endif
> +
> +/* Make sure we only define these if the kernel header doesn't.  */
> +#ifndef GCS_MAGIC
> +
> +/* GCS state (NT_ARM_GCS).  */
> +
> +struct user_gcs
> +{
> +  uint64_t features_enabled;
> +  uint64_t features_locked;
> +  uint64_t gcspr_el0;
> +};
> +
> +#endif /* GCS_MAGIC */
> +
> +#endif /* ARCH_AARCH64_GCS_LINUX_H */
> diff --git a/gdb/arch/aarch64.c b/gdb/arch/aarch64.c index
> 3e1ca0547340..dff2bc16003a 100644
> --- a/gdb/arch/aarch64.c
> +++ b/gdb/arch/aarch64.c
> @@ -26,6 +26,8 @@
>  #include "../features/aarch64-sme.c"
>  #include "../features/aarch64-sme2.c"
>  #include "../features/aarch64-tls.c"
> +#include "../features/aarch64-gcs.c"
> +#include "../features/aarch64-gcs-linux.c"
> 
>  /* See arch/aarch64.h.  */
> 
> @@ -65,6 +67,12 @@ aarch64_create_target_description (const
> aarch64_features &features)
>    if (features.sme2)
>      regnum = create_feature_aarch64_sme2 (tdesc.get (), regnum);
> 
> +  if (features.gcs)
> +    regnum = create_feature_aarch64_gcs (tdesc.get (), regnum);
> +
> +  if (features.gcs_linux)
> +    regnum = create_feature_aarch64_gcs_linux (tdesc.get (), regnum);
> +
>    return tdesc.release ();
>  }
> 
> diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h index
> ee18b74b80f5..679d845df74e 100644
> --- a/gdb/arch/aarch64.h
> +++ b/gdb/arch/aarch64.h
> @@ -51,6 +51,12 @@ struct aarch64_features
> 
>    /* Whether SME2 is supported.  */
>    bool sme2 = false;
> +
> +  /* Whether Guarded Control Stack is supported.  */  bool gcs = false;
> +
> +  /* Whether Guarded Control Stack Linux features are supported.  */
> + bool gcs_linux = false;
>  };
> 
>  inline bool operator==(const aarch64_features &lhs, const aarch64_features
> &rhs) @@ -60,7 +66,9 @@ inline bool operator==(const aarch64_features &lhs,
> const aarch64_features &rhs)
>      && lhs.mte == rhs.mte
>      && lhs.tls == rhs.tls
>      && lhs.svq == rhs.svq
> -    && lhs.sme2 == rhs.sme2;
> +    && lhs.sme2 == rhs.sme2
> +    && lhs.gcs == rhs.gcs
> +    && lhs.gcs_linux == rhs.gcs_linux;
>  }
> 
>  namespace std
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
> c7b57cf22505..03f419e90436 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -26979,6 +26979,21 @@ information automatically from the core file, and
> will show one of the above  messages depending on whether the synchronous or
> asynchronous mode is selected.
>  @xref{Memory Tagging}. @xref{Memory}.
> 
> +@subsubsection Guarded Control Stack
> +@cindex AArch64 GCS
> +@cindex AArch64 Guarded Control Stack
> +
> +When @value{GDBN} is debugging the AArch64 architecture, the program is
> +using the feature Guarded Control Stack (GCS) and there is support in
> +the kernel for GCS, @value{GDBN} will make a couple of special
> +registers --- @code{gcs_features_enabled} and
> +@code{gcs_features_locked} --- available through the
> +@code{org.gnu.gdb.aarch64.gcs.linux} feature.  These registers expose
> +some options that can be controlled at runtime and emulate the
> +@code{prctl} option @code{PR_SET_SHADOW_STACK_STATUS}.  For further
> +information, see the
> +@uref{https://www.kernel.org/doc/html/latest/arch/arm64/gcs.html,ignore
> +d,
> +documentation} in the Linux kernel.
> +
>  @node x86
>  @subsection x86
> 
> @@ -49655,6 +49670,42 @@ of bytes.
>  Extra registers are allowed in this feature, but they will not affect
> @value{GDBN}.
> 
> +@subsubsection GCS registers
> +
> +The @samp{org.gnu.gdb.aarch64.gcs} feature is optional.  If present, it
> +means the target supports Guarded Control Stacks and must contain the
> +following register:
> +
> +@itemize @minus
> +
> +@item
> +@code{gcspr}, which points to the thread's Guarded Control Stack.  It
> +is 64 bits in size and has a type of @samp{data_ptr}.
> +
> +@end itemize
> +
> +The @samp{org.gnu.gdb.aarch64.gcs.linux} feature is optional.  If
> +present, then the @samp{org.gnu.gdb.aarch64.gcs} feature must also be
> +present.  The @samp{org.gnu.gdb.aarch64.gcs.linux} feature represents
> +facilities provided by the Linux kernel for GCS support and should contain the
> following:
> +
> +@itemize @minus
> +
> +@item
> +@code{gcs_features_enabled} shows the features currently enabled via
> +the prctl or ptrace system calls. It is represented as if it were a
> +64-bit register with a custom flags type.
> +
> +@item
> +@code{gcs_features_locked} shows the features currently locked via the
> +prctl or ptrace system calls. It is represented as if it were a 64-bit
> +register with a custom flags type.
> +
> +@end itemize
> +
> +Extra registers are allowed in these features, but they will not affect
> +@value{GDBN}.
> +
>  @node ARC Features
>  @subsection ARC Features
>  @cindex target descriptions, ARC Features diff --git a/gdb/features/Makefile
> b/gdb/features/Makefile index 2afda1ccd00f..ed20a7537ffb 100644
> --- a/gdb/features/Makefile
> +++ b/gdb/features/Makefile
> @@ -203,6 +203,8 @@ FEATURE_XMLFILES = aarch64-core.xml \
>  	aarch64-fpu.xml \
>  	aarch64-pauth.xml \
>  	aarch64-mte.xml \
> +	aarch64-gcs.xml \
> +	aarch64-gcs-linux.xml \
>  	arc/v1-core.xml \
>  	arc/v1-aux.xml \
>  	arc/v2-core.xml \
> diff --git a/gdb/features/aarch64-gcs-linux.c b/gdb/features/aarch64-gcs-linux.c
> new file mode 100644
> index 000000000000..6b0d25b4518c
> --- /dev/null
> +++ b/gdb/features/aarch64-gcs-linux.c
> @@ -0,0 +1,21 @@
> +/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
> +  Original: aarch64-gcs-linux.xml */
> +
> +#include "gdbsupport/tdesc.h"
> +
> +static int
> +create_feature_aarch64_gcs_linux (struct target_desc *result, long
> +regnum) {
> +  struct tdesc_feature *feature;
> +
> +  feature = tdesc_create_feature (result,
> + "org.gnu.gdb.aarch64.gcs.linux");  tdesc_type_with_fields
> + *type_with_fields;  type_with_fields = tdesc_create_flags (feature,
> + "features_flags", 8);  tdesc_add_flag (type_with_fields, 0,
> + "PR_SHADOW_STACK_ENABLE");  tdesc_add_flag (type_with_fields, 1,
> + "PR_SHADOW_STACK_WRITE");  tdesc_add_flag (type_with_fields, 2,
> + "PR_SHADOW_STACK_PUSH");
> +
> +  tdesc_create_reg (feature, "gcs_features_enabled", regnum++, 1,
> +"system", 64, "features_flags");
> +  tdesc_create_reg (feature, "gcs_features_locked", regnum++, 1,
> +"system", 64, "features_flags");
> +  return regnum;
> +}
> diff --git a/gdb/features/aarch64-gcs-linux.xml b/gdb/features/aarch64-gcs-
> linux.xml
> new file mode 100644
> index 000000000000..8d9d2ceb9260
> --- /dev/null
> +++ b/gdb/features/aarch64-gcs-linux.xml
> @@ -0,0 +1,18 @@
> +<?xml version="1.0"?>
> +<!-- Copyright (C) 2025 Free Software Foundation, Inc.
> +
> +     Copying and distribution of this file, with or without modification,
> +     are permitted in any medium without royalty provided the copyright
> +     notice and this notice are preserved.  -->
> +
> +<!DOCTYPE feature SYSTEM "gdb-target.dtd"> <feature
> +name="org.gnu.gdb.aarch64.gcs.linux">
> +  <flags id="features_flags" size="8">
> +    <field name="PR_SHADOW_STACK_ENABLE" start="0" end="0"/>
> +    <field name="PR_SHADOW_STACK_WRITE" start="1" end="1"/>
> +    <field name="PR_SHADOW_STACK_PUSH" start="2" end="2"/>
> +  </flags>
> +
> +  <reg name="gcs_features_enabled" bitsize="64" type="features_flags"
> +group="system"/>
> +  <reg name="gcs_features_locked" bitsize="64" type="features_flags"
> +group="system"/> </feature>
> diff --git a/gdb/features/aarch64-gcs.c b/gdb/features/aarch64-gcs.c new file
> mode 100644 index 000000000000..2b2caf29cc8c
> --- /dev/null
> +++ b/gdb/features/aarch64-gcs.c
> @@ -0,0 +1,14 @@
> +/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
> +  Original: aarch64-gcs.xml */
> +
> +#include "gdbsupport/tdesc.h"
> +
> +static int
> +create_feature_aarch64_gcs (struct target_desc *result, long regnum) {
> +  struct tdesc_feature *feature;
> +
> +  feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs");
> +  tdesc_create_reg (feature, "gcspr", regnum++, 1, "system", 64,
> +"data_ptr");
> +  return regnum;
> +}
> diff --git a/gdb/features/aarch64-gcs.xml b/gdb/features/aarch64-gcs.xml new
> file mode 100644 index 000000000000..bbee5e001722
> --- /dev/null
> +++ b/gdb/features/aarch64-gcs.xml
> @@ -0,0 +1,11 @@
> +<?xml version="1.0"?>
> +<!-- Copyright (C) 2025 Free Software Foundation, Inc.
> +
> +     Copying and distribution of this file, with or without modification,
> +     are permitted in any medium without royalty provided the copyright
> +     notice and this notice are preserved.  -->
> +
> +<!DOCTYPE feature SYSTEM "gdb-target.dtd"> <feature
> +name="org.gnu.gdb.aarch64.gcs">
> +  <reg name="gcspr" bitsize="64" type="data_ptr" group="system"/>
> +</feature>
> diff --git a/gdbserver/linux-aarch64-low.cc b/gdbserver/linux-aarch64-low.cc
> index 2eb3af659ad1..9feb1914dbd6 100644
> --- a/gdbserver/linux-aarch64-low.cc
> +++ b/gdbserver/linux-aarch64-low.cc
> @@ -39,6 +39,7 @@
> 
>  #include "gdb_proc_service.h"
>  #include "arch/aarch64.h"
> +#include "arch/aarch64-gcs-linux.h"
>  #include "arch/aarch64-mte-linux.h"
>  #include "arch/aarch64-scalable-linux.h"
>  #include "linux-aarch32-tdesc.h"
> @@ -321,6 +322,42 @@ aarch64_store_tlsregset (struct regcache *regcache,
> const void *buf)
>      supply_register (regcache, *regnum, tls_buf + sizeof (uint64_t));  }
> 
> +/* Fill BUF with GCS register from the regcache.  */
> +
> +static void
> +aarch64_fill_gcsregset (struct regcache *regcache, void *buf) {
> +  struct user_gcs *regset = (struct user_gcs *) buf;
> +  int gcspr_regnum  = find_regno (regcache->tdesc, "gcspr");
> +  int features_enabled_regnum  = find_regno (regcache->tdesc,
> +					     "gcs_features_enabled");
> +  int features_locked_regnum  = find_regno (regcache->tdesc,
> +					    "gcs_features_locked");
> +
> +  collect_register (regcache, gcspr_regnum, &regset->gcspr_el0);
> +  collect_register (regcache, features_enabled_regnum,
> +		    &regset->features_enabled);
> +  collect_register (regcache, features_locked_regnum,
> +&regset->features_locked); }
> +
> +/* Store GCS register to regcache.  */
> +
> +static void
> +aarch64_store_gcsregset (struct regcache *regcache, const void *buf) {
> +  const struct user_gcs *regset = (const struct user_gcs *) buf;
> +  int gcspr_regnum  = find_regno (regcache->tdesc, "gcspr");
> +  int features_enabled_regnum  = find_regno (regcache->tdesc,
> +					     "gcs_features_enabled");
> +  int features_locked_regnum  = find_regno (regcache->tdesc,
> +					    "gcs_features_locked");
> +
> +  supply_register (regcache, gcspr_regnum, &regset->gcspr_el0);
> +  supply_register (regcache, features_enabled_regnum,
> +		   &regset->features_enabled);
> +  supply_register (regcache, features_locked_regnum,
> +&regset->features_locked); }
> +
>  bool
>  aarch64_target::low_supports_breakpoints ()  { @@ -846,6 +883,10 @@ static
> struct regset_info aarch64_regsets[] =
>    { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_TLS,
>      0, OPTIONAL_REGS,
>      aarch64_fill_tlsregset, aarch64_store_tlsregset },
> +  /* Guarded Control Stack registers.  */  { PTRACE_GETREGSET,
> + PTRACE_SETREGSET, NT_ARM_GCS,
> +    0, OPTIONAL_REGS,
> +    aarch64_fill_gcsregset, aarch64_store_gcsregset },
>    NULL_REGSET
>  };
> 
> @@ -909,6 +950,10 @@ aarch64_adjust_register_sets (const struct
> aarch64_features &features)
>  	  if (features.sme2)
>  	    regset->size = AARCH64_SME2_ZT0_SIZE;
>  	  break;
> +	case NT_ARM_GCS:
> +	  if (features.gcs_linux)
> +	    regset->size = sizeof (struct user_gcs);
> +	  break;
>  	default:
>  	  gdb_assert_not_reached ("Unknown register set found.");
>  	}
> @@ -940,6 +985,7 @@ aarch64_target::low_arch_setup ()
>        /* A-profile MTE is 64-bit only.  */
>        features.mte = linux_get_hwcap2 (pid, 8) & HWCAP2_MTE;
>        features.tls = aarch64_tls_register_count (tid);
> +      features.gcs = features.gcs_linux = linux_get_hwcap (pid, 8) &
> + HWCAP_GCS;
> 
>        /* Scalable Matrix Extension feature and size check.  */
>        if (linux_get_hwcap2 (pid, 8) & HWCAP2_SME)


Kind Regards,
Christina
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
Luis Machado June 13, 2025, 4:17 p.m. UTC | #2
Hi,

On 6/8/25 02:03, Thiago Jung Bauermann wrote:
> @@ -4590,6 +4643,46 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
>      int first_w_regnum = num_pseudo_regs;
>      num_pseudo_regs += 31;
>  
> +  const struct tdesc_feature *feature_gcs
> +      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs");
> +  int first_gcs_regnum = -1;
> +  /* Add the GCS registers.  */
> +  if (feature_gcs != nullptr)
> +    {
> +      first_gcs_regnum = num_regs;
> +      /* Validate the descriptor provides the mandatory GCS registers and
> +	 allocate their numbers.  */
> +      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_register_names); i++)
> +	valid_p &= tdesc_numbered_register (feature_gcs, tdesc_data.get (),
> +					    first_gcs_regnum + i,
> +					    aarch64_gcs_register_names[i]);
> +
> +      num_regs += i;
> +    }
> +
> +  if (!valid_p)
> +    return nullptr;
> +
> +  const struct tdesc_feature *feature_gcs_linux
> +      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux");
> +  int first_gcs_linux_regnum = -1;
> +  /* Add the GCS Linux registers.  */
> +  if (feature_gcs_linux != nullptr)
> +    {
> +      first_gcs_linux_regnum = num_regs;
> +      /* Validate the descriptor provides the mandatory GCS Linux registers
> +	 and allocate their numbers.  */
> +      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_linux_register_names); i++)
> +	valid_p &= tdesc_numbered_register (feature_gcs_linux, tdesc_data.get (),
> +					    first_gcs_linux_regnum + i,
> +					    aarch64_gcs_linux_register_names[i]);
> +
> +      /* This feature depends on the GCS feature.  */
> +      valid_p &= feature_gcs != nullptr;
> +
> +      num_regs += i;
> +    }
> +

Given we have a Linux-specific part of the target description XML, shouldn't it be handled
in aarch64-linux-tdep.c instead? That's what amd64 seems to do. Though s390 opted to do it
in s390-tdep.c as well. No strong preference here, as long as the Linux-specific bit doesn't
get in the way of bare metal handling.

If we know we're gonna need more Linux-specific registers, then it might make sense to split
things into aarch64-tdep.c and aarch64-linux-tdep.c.

>    if (!valid_p)
>      return nullptr;
>  
> @@ -4611,6 +4704,8 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
>    tdep->mte_reg_base = first_mte_regnum;
>    tdep->tls_regnum_base = first_tls_regnum;
>    tdep->tls_register_count = tls_register_count;
> +  tdep->gcs_reg_base = first_gcs_regnum;
> +  tdep->gcs_linux_reg_base = first_gcs_linux_regnum;
>  
>    /* Set the SME register set details.  The pseudo-registers will be adjusted
>       later.  */
> @@ -4733,6 +4828,9 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
>  
>    set_gdbarch_get_pc_address_flags (gdbarch, aarch64_get_pc_address_flags);
>  
> +  if (tdep->has_gcs ())
> +    set_gdbarch_shadow_stack_push (gdbarch, aarch64_shadow_stack_push);
> +
>    tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data));
>  
>    /* Fetch the updated number of registers after we're done adding all
> @@ -4905,6 +5003,9 @@ aarch64_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file)
>  	      pulongest (tdep->sme_tile_pseudo_base));
>    gdb_printf (file, _("aarch64_dump_tdep: sme_svq = %s\n"),
>  	      pulongest (tdep->sme_svq));
> +
> +  gdb_printf (file, _ ("aarch64_dump_tdep: gcs_reg_base = %d\n"),
> +	      tdep->gcs_reg_base);
>  }

Do we need to dump the Linux-specific registers as well?

>  
>  #if GDB_SELF_TEST
> diff --git a/gdb/aarch64-tdep.h b/gdb/aarch64-tdep.h
> index 3b8dcc26545b..0332e80da7bc 100644
> --- a/gdb/aarch64-tdep.h
> +++ b/gdb/aarch64-tdep.h
> @@ -182,6 +182,20 @@ struct aarch64_gdbarch_tdep : gdbarch_tdep_base
>    {
>      return sme2_zt0_regnum > 0;
>    }
> +
> +  /* First GCS register.  This is -1 if no GCS registers are available.  */
> +  int gcs_reg_base = -1;
> +
> +  /* First GCS Linux-specific register.  This is -1 if no GCS Linux feature is
> +     available.  */
> +  int gcs_linux_reg_base = -1;
> +
> +  /* Returns true if the target supports GCS.  */
> +  bool
> +  has_gcs () const
> +  {
> +    return gcs_reg_base != -1;
> +  }
>  };
>  
>  const target_desc *aarch64_read_description (const aarch64_features &features);
> diff --git a/gdb/arch/aarch64-gcs-linux.h b/gdb/arch/aarch64-gcs-linux.h
> new file mode 100644
> index 000000000000..9366caa7289a
> --- /dev/null
> +++ b/gdb/arch/aarch64-gcs-linux.h
> @@ -0,0 +1,44 @@
> +/* Common Linux target-dependent definitions for AArch64 GCS
> +
> +   Copyright (C) 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/>.  */
> +
> +#ifndef ARCH_AARCH64_GCS_LINUX_H
> +#define ARCH_AARCH64_GCS_LINUX_H
> +
> +#include <stdint.h>
> +
> +/* Feature check for Guarded Control Stack.  */
> +#ifndef HWCAP_GCS
> +#define HWCAP_GCS (1UL << 32)
> +#endif
> +
> +/* Make sure we only define these if the kernel header doesn't.  */
> +#ifndef GCS_MAGIC
> +
> +/* GCS state (NT_ARM_GCS).  */
> +
> +struct user_gcs
> +{
> +  uint64_t features_enabled;
> +  uint64_t features_locked;
> +  uint64_t gcspr_el0;
> +};
> +
> +#endif /* GCS_MAGIC */
> +
> +#endif /* ARCH_AARCH64_GCS_LINUX_H */
> diff --git a/gdb/arch/aarch64.c b/gdb/arch/aarch64.c
> index 3e1ca0547340..dff2bc16003a 100644
> --- a/gdb/arch/aarch64.c
> +++ b/gdb/arch/aarch64.c
> @@ -26,6 +26,8 @@
>  #include "../features/aarch64-sme.c"
>  #include "../features/aarch64-sme2.c"
>  #include "../features/aarch64-tls.c"
> +#include "../features/aarch64-gcs.c"
> +#include "../features/aarch64-gcs-linux.c"
>  
>  /* See arch/aarch64.h.  */
>  
> @@ -65,6 +67,12 @@ aarch64_create_target_description (const aarch64_features &features)
>    if (features.sme2)
>      regnum = create_feature_aarch64_sme2 (tdesc.get (), regnum);
>  
> +  if (features.gcs)
> +    regnum = create_feature_aarch64_gcs (tdesc.get (), regnum);
> +
> +  if (features.gcs_linux)
> +    regnum = create_feature_aarch64_gcs_linux (tdesc.get (), regnum);
> +
>    return tdesc.release ();
>  }
>  
> diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h
> index ee18b74b80f5..679d845df74e 100644
> --- a/gdb/arch/aarch64.h
> +++ b/gdb/arch/aarch64.h
> @@ -51,6 +51,12 @@ struct aarch64_features
>  
>    /* Whether SME2 is supported.  */
>    bool sme2 = false;
> +
> +  /* Whether Guarded Control Stack is supported.  */
> +  bool gcs = false;
> +
> +  /* Whether Guarded Control Stack Linux features are supported.  */
> +  bool gcs_linux = false;
>  };
>  
>  inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs)
> @@ -60,7 +66,9 @@ inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs)
>      && lhs.mte == rhs.mte
>      && lhs.tls == rhs.tls
>      && lhs.svq == rhs.svq
> -    && lhs.sme2 == rhs.sme2;
> +    && lhs.sme2 == rhs.sme2
> +    && lhs.gcs == rhs.gcs
> +    && lhs.gcs_linux == rhs.gcs_linux;
>  }
>  
>  namespace std
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index c7b57cf22505..03f419e90436 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -26979,6 +26979,21 @@ information automatically from the core file, and will show one of the above
>  messages depending on whether the synchronous or asynchronous mode is selected.
>  @xref{Memory Tagging}. @xref{Memory}.
>  
> +@subsubsection Guarded Control Stack
> +@cindex AArch64 GCS
> +@cindex AArch64 Guarded Control Stack
> +
> +When @value{GDBN} is debugging the AArch64 architecture, the program is
> +using the feature Guarded Control Stack (GCS) and there is support in the
> +kernel for GCS, @value{GDBN} will make a couple of special registers ---
> +@code{gcs_features_enabled} and @code{gcs_features_locked} --- available
> +through the @code{org.gnu.gdb.aarch64.gcs.linux} feature.  These registers
> +expose some options that can be controlled at runtime and emulate the
> +@code{prctl} option @code{PR_SET_SHADOW_STACK_STATUS}.  For further
> +information, see the
> +@uref{https://www.kernel.org/doc/html/latest/arch/arm64/gcs.html,ignored,
> +documentation} in the Linux kernel.

Isn't the gcspr register also available? It is also worth making it clear the
additional registers will only be available for Linux, whereas non-Linux might
only see the gcspr (if it is visible).


> +
>  @node x86
>  @subsection x86
>  
> @@ -49655,6 +49670,42 @@ of bytes.
>  Extra registers are allowed in this feature, but they will not affect
>  @value{GDBN}.
>  
> +@subsubsection GCS registers
> +
> +The @samp{org.gnu.gdb.aarch64.gcs} feature is optional.  If present, it
> +means the target supports Guarded Control Stacks and must contain the
> +following register:
> +
> +@itemize @minus
> +
> +@item
> +@code{gcspr}, which points to the thread's Guarded Control Stack.  It is 64
> +bits in size and has a type of @samp{data_ptr}.
> +
> +@end itemize
> +
> +The @samp{org.gnu.gdb.aarch64.gcs.linux} feature is optional.  If present,
> +then the @samp{org.gnu.gdb.aarch64.gcs} feature must also be present.  The
> +@samp{org.gnu.gdb.aarch64.gcs.linux} feature represents facilities provided
> +by the Linux kernel for GCS support and should contain the following:
> +
> +@itemize @minus
> +
> +@item
> +@code{gcs_features_enabled} shows the features currently enabled via the
> +prctl or ptrace system calls. It is represented as if it were a 64-bit

two spaces after period.

> +register with a custom flags type.
> +
> +@item
> +@code{gcs_features_locked} shows the features currently locked via the
> +prctl or ptrace system calls. It is represented as if it were a 64-bit
> +register with a custom flags type.

two spaces after period.

> +
> +@end itemize
> +
> +Extra registers are allowed in these features, but they will not affect
> +@value{GDBN}.
> +
>  @node ARC Features
>  @subsection ARC Features
>  @cindex target descriptions, ARC Features
> diff --git a/gdb/features/Makefile b/gdb/features/Makefile
> index 2afda1ccd00f..ed20a7537ffb 100644
> --- a/gdb/features/Makefile
> +++ b/gdb/features/Makefile
> @@ -203,6 +203,8 @@ FEATURE_XMLFILES = aarch64-core.xml \
>  	aarch64-fpu.xml \
>  	aarch64-pauth.xml \
>  	aarch64-mte.xml \
> +	aarch64-gcs.xml \
> +	aarch64-gcs-linux.xml \
>  	arc/v1-core.xml \
>  	arc/v1-aux.xml \
>  	arc/v2-core.xml \
> diff --git a/gdb/features/aarch64-gcs-linux.c b/gdb/features/aarch64-gcs-linux.c
> new file mode 100644
> index 000000000000..6b0d25b4518c
> --- /dev/null
> +++ b/gdb/features/aarch64-gcs-linux.c
> @@ -0,0 +1,21 @@
> +/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
> +  Original: aarch64-gcs-linux.xml */
> +
> +#include "gdbsupport/tdesc.h"
> +
> +static int
> +create_feature_aarch64_gcs_linux (struct target_desc *result, long regnum)
> +{
> +  struct tdesc_feature *feature;
> +
> +  feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs.linux");
> +  tdesc_type_with_fields *type_with_fields;
> +  type_with_fields = tdesc_create_flags (feature, "features_flags", 8);
> +  tdesc_add_flag (type_with_fields, 0, "PR_SHADOW_STACK_ENABLE");
> +  tdesc_add_flag (type_with_fields, 1, "PR_SHADOW_STACK_WRITE");
> +  tdesc_add_flag (type_with_fields, 2, "PR_SHADOW_STACK_PUSH");
> +

Would it be worth documenting these 3 flags in the manual?

> +  tdesc_create_reg (feature, "gcs_features_enabled", regnum++, 1, "system", 64, "features_flags");
> +  tdesc_create_reg (feature, "gcs_features_locked", regnum++, 1, "system", 64, "features_flags");
> +  return regnum;
> +}
> diff --git a/gdb/features/aarch64-gcs-linux.xml b/gdb/features/aarch64-gcs-linux.xml
> new file mode 100644
> index 000000000000..8d9d2ceb9260
> --- /dev/null
> +++ b/gdb/features/aarch64-gcs-linux.xml
> @@ -0,0 +1,18 @@
> +<?xml version="1.0"?>
> +<!-- Copyright (C) 2025 Free Software Foundation, Inc.
> +
> +     Copying and distribution of this file, with or without modification,
> +     are permitted in any medium without royalty provided the copyright
> +     notice and this notice are preserved.  -->
> +
> +<!DOCTYPE feature SYSTEM "gdb-target.dtd">
> +<feature name="org.gnu.gdb.aarch64.gcs.linux">
> +  <flags id="features_flags" size="8">
> +    <field name="PR_SHADOW_STACK_ENABLE" start="0" end="0"/>
> +    <field name="PR_SHADOW_STACK_WRITE" start="1" end="1"/>
> +    <field name="PR_SHADOW_STACK_PUSH" start="2" end="2"/>
> +  </flags>
> +
> +  <reg name="gcs_features_enabled" bitsize="64" type="features_flags" group="system"/>
> +  <reg name="gcs_features_locked" bitsize="64" type="features_flags" group="system"/>
> +</feature>
> diff --git a/gdb/features/aarch64-gcs.c b/gdb/features/aarch64-gcs.c
> new file mode 100644
> index 000000000000..2b2caf29cc8c
> --- /dev/null
> +++ b/gdb/features/aarch64-gcs.c
> @@ -0,0 +1,14 @@
> +/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
> +  Original: aarch64-gcs.xml */
> +
> +#include "gdbsupport/tdesc.h"
> +
> +static int
> +create_feature_aarch64_gcs (struct target_desc *result, long regnum)
> +{
> +  struct tdesc_feature *feature;
> +
> +  feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs");
> +  tdesc_create_reg (feature, "gcspr", regnum++, 1, "system", 64, "data_ptr");
> +  return regnum;
> +}
> diff --git a/gdb/features/aarch64-gcs.xml b/gdb/features/aarch64-gcs.xml
> new file mode 100644
> index 000000000000..bbee5e001722
> --- /dev/null
> +++ b/gdb/features/aarch64-gcs.xml
> @@ -0,0 +1,11 @@
> +<?xml version="1.0"?>
> +<!-- Copyright (C) 2025 Free Software Foundation, Inc.
> +
> +     Copying and distribution of this file, with or without modification,
> +     are permitted in any medium without royalty provided the copyright
> +     notice and this notice are preserved.  -->
> +
> +<!DOCTYPE feature SYSTEM "gdb-target.dtd">
> +<feature name="org.gnu.gdb.aarch64.gcs">
> +  <reg name="gcspr" bitsize="64" type="data_ptr" group="system"/>
> +</feature>
> diff --git a/gdbserver/linux-aarch64-low.cc b/gdbserver/linux-aarch64-low.cc
> index 2eb3af659ad1..9feb1914dbd6 100644
> --- a/gdbserver/linux-aarch64-low.cc
> +++ b/gdbserver/linux-aarch64-low.cc
> @@ -39,6 +39,7 @@
>  
>  #include "gdb_proc_service.h"
>  #include "arch/aarch64.h"
> +#include "arch/aarch64-gcs-linux.h"
>  #include "arch/aarch64-mte-linux.h"
>  #include "arch/aarch64-scalable-linux.h"
>  #include "linux-aarch32-tdesc.h"
> @@ -321,6 +322,42 @@ aarch64_store_tlsregset (struct regcache *regcache, const void *buf)
>      supply_register (regcache, *regnum, tls_buf + sizeof (uint64_t));
>  }
>  
> +/* Fill BUF with GCS register from the regcache.  */
> +
> +static void
> +aarch64_fill_gcsregset (struct regcache *regcache, void *buf)
> +{
> +  struct user_gcs *regset = (struct user_gcs *) buf;
> +  int gcspr_regnum  = find_regno (regcache->tdesc, "gcspr");
> +  int features_enabled_regnum  = find_regno (regcache->tdesc,
> +					     "gcs_features_enabled");
> +  int features_locked_regnum  = find_regno (regcache->tdesc,
> +					    "gcs_features_locked");
> +
> +  collect_register (regcache, gcspr_regnum, &regset->gcspr_el0);
> +  collect_register (regcache, features_enabled_regnum,
> +		    &regset->features_enabled);
> +  collect_register (regcache, features_locked_regnum, &regset->features_locked);
> +}
> +
> +/* Store GCS register to regcache.  */
> +
> +static void
> +aarch64_store_gcsregset (struct regcache *regcache, const void *buf)
> +{
> +  const struct user_gcs *regset = (const struct user_gcs *) buf;
> +  int gcspr_regnum  = find_regno (regcache->tdesc, "gcspr");
> +  int features_enabled_regnum  = find_regno (regcache->tdesc,
> +					     "gcs_features_enabled");
> +  int features_locked_regnum  = find_regno (regcache->tdesc,
> +					    "gcs_features_locked");
> +
> +  supply_register (regcache, gcspr_regnum, &regset->gcspr_el0);
> +  supply_register (regcache, features_enabled_regnum,
> +		   &regset->features_enabled);
> +  supply_register (regcache, features_locked_regnum, &regset->features_locked);
> +}
> +
>  bool
>  aarch64_target::low_supports_breakpoints ()
>  {
> @@ -846,6 +883,10 @@ static struct regset_info aarch64_regsets[] =
>    { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_TLS,
>      0, OPTIONAL_REGS,
>      aarch64_fill_tlsregset, aarch64_store_tlsregset },
> +  /* Guarded Control Stack registers.  */
> +  { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_GCS,
> +    0, OPTIONAL_REGS,
> +    aarch64_fill_gcsregset, aarch64_store_gcsregset },
>    NULL_REGSET
>  };
>  
> @@ -909,6 +950,10 @@ aarch64_adjust_register_sets (const struct aarch64_features &features)
>  	  if (features.sme2)
>  	    regset->size = AARCH64_SME2_ZT0_SIZE;
>  	  break;
> +	case NT_ARM_GCS:
> +	  if (features.gcs_linux)
> +	    regset->size = sizeof (struct user_gcs);
> +	  break;
>  	default:
>  	  gdb_assert_not_reached ("Unknown register set found.");
>  	}
> @@ -940,6 +985,7 @@ aarch64_target::low_arch_setup ()
>        /* A-profile MTE is 64-bit only.  */
>        features.mte = linux_get_hwcap2 (pid, 8) & HWCAP2_MTE;
>        features.tls = aarch64_tls_register_count (tid);
> +      features.gcs = features.gcs_linux = linux_get_hwcap (pid, 8) & HWCAP_GCS;
>  
>        /* Scalable Matrix Extension feature and size check.  */
>        if (linux_get_hwcap2 (pid, 8) & HWCAP2_SME)

Otherwise the rest of the code looks OK to me.

Thanks for the patch.

Reviewed-By: Luis Machado <luis.machado@arm.com>
Thiago Jung Bauermann June 14, 2025, 1:44 a.m. UTC | #3
Hello Christina,

"Schimpe, Christina" <christina.schimpe@intel.com> writes:

> Please see my comments below.

Thank you for your review!

>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Sunday, June 8, 2025 3:03 AM
>> To: gdb-patches@sourceware.org
>> Subject: [PATCH 3/8] GDB, gdbserver: aarch64-linux: Initial Guarded Control Stack
>> support
>> 
>> Add the org.gnu.gdb.aarch64.gcs feature with the GCSPR register, and the
>> org.gnu.gdb.aarch64.gcs.linux feature with "registers" to represent the Linux
>> kernel ptrace and prctl knobs that enable and lock specific GCS functionality.
>
> You only add the el0 register? Then you could mention this explicitly in the commit
> message I think.
> Or is there only userspace support for GCS ?

Indeed, these patches only add support for GCS in Linux userspace
applications. I'll mention that only the EL0 register is exposed in GDB,
both in the commit message as well as in the manual (since Luis
suggested mentioning the GCSPR in the documentation).

>> diff --git a/gdb/aarch64-linux-nat.c b/gdb/aarch64-linux-nat.c index
>> d7869f42e825..0796b1e47955 100644
>> --- a/gdb/aarch64-linux-nat.c
>> +++ b/gdb/aarch64-linux-nat.c
>> @@ -51,6 +51,7 @@
>>  #include "gdb_proc_service.h"
>>  #include "arch-utils.h"
>> 
>> +#include "arch/aarch64-gcs-linux.h"
>>  #include "arch/aarch64-mte-linux.h"
>> 
>>  #include "nat/aarch64-mte-linux-ptrace.h"
>> @@ -542,6 +543,67 @@ store_tlsregs_to_thread (struct regcache *regcache)
>>      perror_with_name (_("unable to store TLS register"));  }
>> 
>> +/* Fill GDB's register array with the GCS register values from
>> +   the current thread.  */
>> +
>> +static void
>> +fetch_gcsregs_from_thread (struct regcache *regcache) {
>> +  aarch64_gdbarch_tdep *tdep
>> +    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
>>
>
> Nit: is the "struct" keyword necessary ? 
> struct regcache *regcache => regcache *regcache

Fixed, thanks.

>> +  gdb_assert (tdep->gcs_reg_base != -1);  gdb_assert
>> + (tdep->gcs_linux_reg_base != -1);
>> +
>> +  struct user_gcs user_gcs;
>> +  struct iovec iovec;
>> 
>
> Also here:
> struct user_gcs user_gcs; => user_gcs user_gcs;
> struct iovec iovec; => iovec iovec;
>
> There are some more occurrences in this patch, but I'll stop here
> to comment on each of them.

Fixed here and also the other occurrences, except in generated source
files. There weren't occurrences in the other patches.

>> +  iovec.iov_base = &user_gcs;
>> +  iovec.iov_len = sizeof (user_gcs);
>> +
>> +  int tid = get_ptrace_pid (regcache->ptid ());  if (ptrace
>> + (PTRACE_GETREGSET, tid, NT_ARM_GCS, &iovec) != 0)
>> +      perror_with_name (_("unable to fetch GCS registers"));
>
> Nit: I think the ident is only 2 spaces?

You're right. Fixed.

>> +  regcache->raw_supply (tdep->gcs_reg_base, &user_gcs.gcspr_el0);
>> +  regcache->raw_supply (tdep->gcs_linux_reg_base,
>> +&user_gcs.features_enabled);
>> +  regcache->raw_supply (tdep->gcs_linux_reg_base + 1,
>> +			&user_gcs.features_locked);
>> +}
>> +
>> +/* Store to the current thread the valid GCS register set in the GDB's
>> +   register array.  */
>> +
>> +static void
>> +store_gcsregs_to_thread (struct regcache *regcache) {
>> +  aarch64_gdbarch_tdep *tdep
>> +    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
>> +
>> +  gdb_assert (tdep->gcs_reg_base != -1);  gdb_assert
>> + (tdep->gcs_linux_reg_base != -1);
>> +
>> +  if (REG_VALID != regcache->get_register_status (tdep->gcs_reg_base)
>> +      || REG_VALID != regcache->get_register_status (tdep->gcs_linux_reg_base)
>> +      || REG_VALID
>> +	     != regcache->get_register_status (tdep->gcs_linux_reg_base + 1))
>> +    return;
>
> Nit: I think the ident should only be 2 spaces, after the level of REG_VALID.

I think so. This is one awkward line. Fixed.

>> +  struct user_gcs user_gcs;
>> +  regcache->raw_collect (tdep->gcs_reg_base, &user_gcs.gcspr_el0);
>> +  regcache->raw_collect (tdep->gcs_linux_reg_base,
>> +&user_gcs.features_enabled);
>> +  regcache->raw_collect (tdep->gcs_linux_reg_base + 1,
>> +			 &user_gcs.features_locked);
>> +
>> +  struct iovec iovec;
>> +  iovec.iov_base = &user_gcs;
>> +  iovec.iov_len = sizeof (user_gcs);
>> +
>> +  int tid = get_ptrace_pid (regcache->ptid ());
>> +  if (ptrace (PTRACE_SETREGSET, tid, NT_ARM_GCS, &iovec) != 0)
>> +    perror_with_name (_("unable to store GCS registers")); }
>> +
>>  /* The AArch64 version of the "fetch_registers" target_ops method.  Fetch
>>     REGNO from the target and place the result into REGCACHE.  */
>> 
>> @@ -577,6 +639,9 @@ aarch64_fetch_registers (struct regcache *regcache, int
>> regno)
>> 
>>        if (tdep->has_sme2 ())
>>  	fetch_zt_from_thread (regcache);
>> +
>> +      if (tdep->has_gcs ())
>> +	fetch_gcsregs_from_thread (regcache);
>
> So you call the method fetch_gcsregs_from_thread which lives inside a *-linux-* file and
> also uses "registers" of the org.gnu.gdb.aarch64.gcs.linux feature.  Shouldn't has_gcs ()
> also check for the linux specific register gcs_linux_reg_base or am I missing something?
>
> What happens if you try to debug on a machine without linux kernel GCS support but support
> in HW?

If the kernel doesn't support GCS, then it won't set HWCAP_GCS in AT_HWCAP
and userspace won't use GCS.

It shouldn't happen in GDB that a target description has
org.gnu.gdb.aarch64.gcs.linux but not org.gnu.gdb.aarch64.gcs.  To make
sure of it, for v2 I'm adding the following check to
aarch64_linux_init_abi:

  if (tdep->has_gcs () && !tdep->has_gcs_linux ())
    error (_("Incomplete GCS support in the target: missing Linux feature"));

Or something equivalent, depending on how I will address Luis' suggestion
that I handle setup of org.gnu.gdb.aarch64.gcs in aarch64-linux-tdep.c.

I also changed the if condition to check for tdep->has_gcs_linux ()
instead of tdep->has_gcs ().

>>      }
>>    /* General purpose register?  */
>>    else if (regno < AARCH64_V0_REGNUM)
>> @@ -609,6 +674,11 @@ aarch64_fetch_registers (struct regcache *regcache, int
>> regno)
>>  	   && regno >= tdep->tls_regnum_base
>>  	   && regno < tdep->tls_regnum_base + tdep->tls_register_count)
>>      fetch_tlsregs_from_thread (regcache);
>> +  /* GCS register?  */
>> +  else if (tdep->has_gcs ()
>> +	   && (regno == tdep->gcs_reg_base || regno == tdep-
>> >gcs_linux_reg_base
>> +	       || regno == tdep->gcs_linux_reg_base + 1))
>> +    fetch_gcsregs_from_thread (regcache);
>>  }
>> 
>>  /* A version of the "fetch_registers" target_ops method used when running @@
>> -680,6 +750,9 @@ aarch64_store_registers (struct regcache *regcache, int
>> regno)
>> 
>>        if (tdep->has_sme2 ())
>>  	store_zt_to_thread (regcache);
>> +
>> +      if (tdep->has_gcs ())
>> +	store_gcsregs_to_thread (regcache);
>
> Similar comment for the linux specific "registers".

Here too I changed the if condition to check for tdep->has_gcs_linux ()
instead of tdep->has_gcs ().

>>      }
>>    /* General purpose register?  */
>>    else if (regno < AARCH64_V0_REGNUM)

<snip>

>> +/* Push LR_VALUE to the Guarded Control Stack.  */
>> +
>> +static void
>> +aarch64_push_gcs_entry (regcache *regs, CORE_ADDR lr_value) {
>> +  gdbarch *arch = regs->arch ();
>> +  aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep>
>> +(arch);
>> +  CORE_ADDR gcs_addr;
>> +
>> +  enum register_status status = regs->cooked_read (tdep->gcs_reg_base,
>> +						   &gcs_addr);
>> +  if (status != REG_VALID)
>> +    error ("Can't read $gcspr.");
>
> Nit: Shoudn't this better be:
>  error (_("Can't read $gcspr."));, 
> to make it i18n friendly?

You're right, thanks for noticing. Fixed.

>> +  gcs_addr -= 8;
>> +  gdb_byte buf[8];
>> +  store_integer (buf, gdbarch_byte_order (arch), lr_value);  if
>> + (target_write_memory (gcs_addr, buf, sizeof (buf)) != 0)
>> +    error ("Can't write to Guarded Control Stack.");
>
> Similar comment for i18n.

Fixed.

>> +  /* Update GCSPR.  */
>> +  regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr);
>> +}
>> +
>> +/* Implement the "shadow_stack_push" gdbarch method.  */
>> +
>> +static void
>> +aarch64_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr,
>> +			   regcache *regcache)
>> +{
>> +  aarch64_push_gcs_entry (regcache, new_addr); }
>
> Can't you simply pass gdbarch to aarch64_push_gcs_entry ?
> Then there is no need for the additional call
>
> gdbarch *arch = regs->arch ()
>
> in aarch64_push_gcs_entry.

I thought a gdbarch argument was redundant considering that it can be
obtained from the regcache. In my view, your suggestion is an optimization
but if it's necessary a better option would be to build GDB with LTO and
let the compiler inline the arch method call. Or move the arch method
definition to the header file.

>> @@ -4590,6 +4643,46 @@ aarch64_gdbarch_init (struct gdbarch_info info,
>> struct gdbarch_list *arches)
>>      int first_w_regnum = num_pseudo_regs;
>>      num_pseudo_regs += 31;
>> 
>> +  const struct tdesc_feature *feature_gcs
>> +      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs");
>> +  int first_gcs_regnum = -1;
>> +  /* Add the GCS registers.  */
>> +  if (feature_gcs != nullptr)
>> +    {
>> +      first_gcs_regnum = num_regs;
>> +      /* Validate the descriptor provides the mandatory GCS registers and
>> +	 allocate their numbers.  */
>> +      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_register_names); i++)
>> +	valid_p &= tdesc_numbered_register (feature_gcs, tdesc_data.get (),
>> +					    first_gcs_regnum + i,
>> +					    aarch64_gcs_register_names[i]);
>> +
>> +      num_regs += i;
>> +    }
>> +
>> +  if (!valid_p)
>> +    return nullptr;
>> +
>> +  const struct tdesc_feature *feature_gcs_linux
>> +      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux");
>> +  int first_gcs_linux_regnum = -1;
>> +  /* Add the GCS Linux registers.  */
>> +  if (feature_gcs_linux != nullptr)
>> +    {
>> +      first_gcs_linux_regnum = num_regs;
>> +      /* Validate the descriptor provides the mandatory GCS Linux registers
>> +	 and allocate their numbers.  */
>> +      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_linux_register_names); i++)
>> +	valid_p &= tdesc_numbered_register (feature_gcs_linux, tdesc_data.get
>> (),
>> +					    first_gcs_linux_regnum + i,
>> +
>> aarch64_gcs_linux_register_names[i]);
>> +
>> +      /* This feature depends on the GCS feature.  */
>> +      valid_p &= feature_gcs != nullptr;
>
> Couldn't you check if feature_gcs != nullptr before
>
>> +      /*  Validate the descriptor provides the mandatory GCS Linux registers
>> +	 and allocate their numbers.  */ 
> [...]
>
> ? Then you could return early.

Indeed. For v2 I changed this part to:

  const tdesc_feature *feature_gcs_linux
    = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux");
  int first_gcs_linux_regnum = -1;
  /* Add the GCS Linux registers.  */
  if (feature_gcs_linux != nullptr && feature_gcs == nullptr)
    /* This feature depends on the GCS feature.  */
    return nullptr;
  else if (feature_gcs_linux != nullptr)
    {
      first_gcs_linux_regnum = num_regs;
      /* Validate the descriptor provides the mandatory GCS Linux registers
	 and allocate their numbers.  */
      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_linux_register_names); i++)
	valid_p &= tdesc_numbered_register (feature_gcs_linux, tdesc_data.get (),
					    first_gcs_linux_regnum + i,
					    aarch64_gcs_linux_register_names[i]);

      num_regs += i;
    }

>> +      num_regs += i;
>> +    }
>> +
>>    if (!valid_p)
>>      return nullptr;
>> 
>> @@ -4611,6 +4704,8 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct
>> gdbarch_list *arches)
>>    tdep->mte_reg_base = first_mte_regnum;
>>    tdep->tls_regnum_base = first_tls_regnum;
>>    tdep->tls_register_count = tls_register_count;
>> +  tdep->gcs_reg_base = first_gcs_regnum;  tdep->gcs_linux_reg_base =
>> + first_gcs_linux_regnum;
>> 
>>    /* Set the SME register set details.  The pseudo-registers will be adjusted
>>       later.  */
>> @@ -4733,6 +4828,9 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct
>> gdbarch_list *arches)
>> 
>>    set_gdbarch_get_pc_address_flags (gdbarch, aarch64_get_pc_address_flags);
>> 
>> +  if (tdep->has_gcs ())
>> +    set_gdbarch_shadow_stack_push (gdbarch, aarch64_shadow_stack_push);
>> +
>
> Do I understand correctly, that you enable inferior calls by implementing this gdbarch
> method here?

Yes, that is correct.

> Then I think it could be worth mentioning in the commit message I think.

Good idea, I added a note about it in the commit message.

> Also I wonder - do you support the return command as well already?
> Cause I couldn't find a specific test for it.

Ah! That's a great observation. Indeed I missed adding support for the
return command. Thank you for bring this up.

I will implement support for it and add a test for the return command
based on the one from your patch series.

>>    tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data));
>> 
>>    /* Fetch the updated number of registers after we're done adding all @@ -
>> 4905,6 +5003,9 @@ aarch64_dump_tdep (struct gdbarch *gdbarch, struct ui_file
>> *file)
>>  	      pulongest (tdep->sme_tile_pseudo_base));
>>    gdb_printf (file, _("aarch64_dump_tdep: sme_svq = %s\n"),
>>  	      pulongest (tdep->sme_svq));
>> +
>> +  gdb_printf (file, _ ("aarch64_dump_tdep: gcs_reg_base = %d\n"),
>> +	      tdep->gcs_reg_base);
>>  }
>
> Nit: no whitespace
> gdb_printf (file, _ ("aarch64_dump_tdep: gcs_reg_base = %d\n")
> => gdb_printf (file, _("aarch64_dump_tdep: gcs_reg_base = %d\n")

Fixed, thanks.
Thiago Jung Bauermann June 14, 2025, 3:25 a.m. UTC | #4
Hello,

Luis Machado <luis.machado@arm.com> writes:

> On 6/8/25 02:03, Thiago Jung Bauermann wrote:
>> @@ -4590,6 +4643,46 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct
> gdbarch_list *arches)
>>      int first_w_regnum = num_pseudo_regs;
>>      num_pseudo_regs += 31;
>>  
>> +  const struct tdesc_feature *feature_gcs
>> +      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs");
>> +  int first_gcs_regnum = -1;
>> +  /* Add the GCS registers.  */
>> +  if (feature_gcs != nullptr)
>> +    {
>> +      first_gcs_regnum = num_regs;
>> +      /* Validate the descriptor provides the mandatory GCS registers and
>> +	 allocate their numbers.  */
>> +      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_register_names); i++)
>> +	valid_p &= tdesc_numbered_register (feature_gcs, tdesc_data.get (),
>> +					    first_gcs_regnum + i,
>> +					    aarch64_gcs_register_names[i]);
>> +
>> +      num_regs += i;
>> +    }
>> +
>> +  if (!valid_p)
>> +    return nullptr;
>> +
>> +  const struct tdesc_feature *feature_gcs_linux
>> +      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux");
>> +  int first_gcs_linux_regnum = -1;
>> +  /* Add the GCS Linux registers.  */
>> +  if (feature_gcs_linux != nullptr)
>> +    {
>> +      first_gcs_linux_regnum = num_regs;
>> +      /* Validate the descriptor provides the mandatory GCS Linux registers
>> +	 and allocate their numbers.  */
>> +      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_linux_register_names); i++)
>> +	valid_p &= tdesc_numbered_register (feature_gcs_linux, tdesc_data.get (),
>> +					    first_gcs_linux_regnum + i,
>> +					    aarch64_gcs_linux_register_names[i]);
>> +
>> +      /* This feature depends on the GCS feature.  */
>> +      valid_p &= feature_gcs != nullptr;
>> +
>> +      num_regs += i;
>> +    }
>> +
>
> Given we have a Linux-specific part of the target description XML, shouldn't it be handled
> in aarch64-linux-tdep.c instead? That's what amd64 seems to do. Though s390 opted to do it
> in s390-tdep.c as well. No strong preference here, as long as the Linux-specific bit
> doesn't
> get in the way of bare metal handling.

Do you mean handling it in aarch64_linux_init_abi? I'll try it out and
see how it looks. I agree it makes more sense to do it in
aarch64-linux-tdep.c

> If we know we're gonna need more Linux-specific registers, then it might make sense to
> split
> things into aarch64-tdep.c and aarch64-linux-tdep.c.

I don't expect more Linux-specific registers, though it's difficult to
make predictions. Especially about the future.

>>    if (!valid_p)
>>      return nullptr;
>>  
>> @@ -4611,6 +4704,8 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct
> gdbarch_list *arches)
>>    tdep->mte_reg_base = first_mte_regnum;
>>    tdep->tls_regnum_base = first_tls_regnum;
>>    tdep->tls_register_count = tls_register_count;
>> +  tdep->gcs_reg_base = first_gcs_regnum;
>> +  tdep->gcs_linux_reg_base = first_gcs_linux_regnum;
>>  
>>    /* Set the SME register set details.  The pseudo-registers will be adjusted
>>       later.  */
>> @@ -4733,6 +4828,9 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct
> gdbarch_list *arches)
>>  
>>    set_gdbarch_get_pc_address_flags (gdbarch, aarch64_get_pc_address_flags);
>>  
>> +  if (tdep->has_gcs ())
>> +    set_gdbarch_shadow_stack_push (gdbarch, aarch64_shadow_stack_push);
>> +
>>    tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data));
>>  
>>    /* Fetch the updated number of registers after we're done adding all
>> @@ -4905,6 +5003,9 @@ aarch64_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file)
>>  	      pulongest (tdep->sme_tile_pseudo_base));
>>    gdb_printf (file, _("aarch64_dump_tdep: sme_svq = %s\n"),
>>  	      pulongest (tdep->sme_svq));
>> +
>> +  gdb_printf (file, _ ("aarch64_dump_tdep: gcs_reg_base = %d\n"),
>> +	      tdep->gcs_reg_base);
>>  }
>
> Do we need to dump the Linux-specific registers as well?

Well spotted. I missed it. Fixed.

>>  #if GDB_SELF_TEST
>> diff --git a/gdb/aarch64-tdep.h b/gdb/aarch64-tdep.h
>> index 3b8dcc26545b..0332e80da7bc 100644
>> --- a/gdb/aarch64-tdep.h
>> +++ b/gdb/aarch64-tdep.h
>> @@ -182,6 +182,20 @@ struct aarch64_gdbarch_tdep : gdbarch_tdep_base
>>    {
>>      return sme2_zt0_regnum > 0;
>>    }
>> +
>> +  /* First GCS register.  This is -1 if no GCS registers are available.  */
>> +  int gcs_reg_base = -1;
>> +
>> +  /* First GCS Linux-specific register.  This is -1 if no GCS Linux feature is
>> +     available.  */
>> +  int gcs_linux_reg_base = -1;
>> +
>> +  /* Returns true if the target supports GCS.  */
>> +  bool
>> +  has_gcs () const
>> +  {
>> +    return gcs_reg_base != -1;
>> +  }
>>  };
>>  
>>  const target_desc *aarch64_read_description (const aarch64_features &features);
>> diff --git a/gdb/arch/aarch64-gcs-linux.h b/gdb/arch/aarch64-gcs-linux.h
>> new file mode 100644
>> index 000000000000..9366caa7289a
>> --- /dev/null
>> +++ b/gdb/arch/aarch64-gcs-linux.h
>> @@ -0,0 +1,44 @@
>> +/* Common Linux target-dependent definitions for AArch64 GCS
>> +
>> +   Copyright (C) 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/>.  */
>> +
>> +#ifndef ARCH_AARCH64_GCS_LINUX_H
>> +#define ARCH_AARCH64_GCS_LINUX_H
>> +
>> +#include <stdint.h>
>> +
>> +/* Feature check for Guarded Control Stack.  */
>> +#ifndef HWCAP_GCS
>> +#define HWCAP_GCS (1UL << 32)
>> +#endif
>> +
>> +/* Make sure we only define these if the kernel header doesn't.  */
>> +#ifndef GCS_MAGIC
>> +
>> +/* GCS state (NT_ARM_GCS).  */
>> +
>> +struct user_gcs
>> +{
>> +  uint64_t features_enabled;
>> +  uint64_t features_locked;
>> +  uint64_t gcspr_el0;
>> +};
>> +
>> +#endif /* GCS_MAGIC */
>> +
>> +#endif /* ARCH_AARCH64_GCS_LINUX_H */
>> diff --git a/gdb/arch/aarch64.c b/gdb/arch/aarch64.c
>> index 3e1ca0547340..dff2bc16003a 100644
>> --- a/gdb/arch/aarch64.c
>> +++ b/gdb/arch/aarch64.c
>> @@ -26,6 +26,8 @@
>>  #include "../features/aarch64-sme.c"
>>  #include "../features/aarch64-sme2.c"
>>  #include "../features/aarch64-tls.c"
>> +#include "../features/aarch64-gcs.c"
>> +#include "../features/aarch64-gcs-linux.c"
>>  
>>  /* See arch/aarch64.h.  */
>>  
>> @@ -65,6 +67,12 @@ aarch64_create_target_description (const aarch64_features &features)
>>    if (features.sme2)
>>      regnum = create_feature_aarch64_sme2 (tdesc.get (), regnum);
>>  
>> +  if (features.gcs)
>> +    regnum = create_feature_aarch64_gcs (tdesc.get (), regnum);
>> +
>> +  if (features.gcs_linux)
>> +    regnum = create_feature_aarch64_gcs_linux (tdesc.get (), regnum);
>> +
>>    return tdesc.release ();
>>  }
>>  
>> diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h
>> index ee18b74b80f5..679d845df74e 100644
>> --- a/gdb/arch/aarch64.h
>> +++ b/gdb/arch/aarch64.h
>> @@ -51,6 +51,12 @@ struct aarch64_features
>>  
>>    /* Whether SME2 is supported.  */
>>    bool sme2 = false;
>> +
>> +  /* Whether Guarded Control Stack is supported.  */
>> +  bool gcs = false;
>> +
>> +  /* Whether Guarded Control Stack Linux features are supported.  */
>> +  bool gcs_linux = false;
>>  };
>>  
>>  inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs)
>> @@ -60,7 +66,9 @@ inline bool operator==(const aarch64_features &lhs, const
> aarch64_features &rhs)
>>      && lhs.mte == rhs.mte
>>      && lhs.tls == rhs.tls
>>      && lhs.svq == rhs.svq
>> -    && lhs.sme2 == rhs.sme2;
>> +    && lhs.sme2 == rhs.sme2
>> +    && lhs.gcs == rhs.gcs
>> +    && lhs.gcs_linux == rhs.gcs_linux;
>>  }
>>  
>>  namespace std
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index c7b57cf22505..03f419e90436 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -26979,6 +26979,21 @@ information automatically from the core file, and will show one
> of the above
>>  messages depending on whether the synchronous or asynchronous mode is selected.
>>  @xref{Memory Tagging}. @xref{Memory}.
>>  
>> +@subsubsection Guarded Control Stack
>> +@cindex AArch64 GCS
>> +@cindex AArch64 Guarded Control Stack
>> +
>> +When @value{GDBN} is debugging the AArch64 architecture, the program is
>> +using the feature Guarded Control Stack (GCS) and there is support in the
>> +kernel for GCS, @value{GDBN} will make a couple of special registers ---
>> +@code{gcs_features_enabled} and @code{gcs_features_locked} --- available
>> +through the @code{org.gnu.gdb.aarch64.gcs.linux} feature.  These registers
>> +expose some options that can be controlled at runtime and emulate the
>> +@code{prctl} option @code{PR_SET_SHADOW_STACK_STATUS}.  For further
>> +information, see the
>> +@uref{https://www.kernel.org/doc/html/latest/arch/arm64/gcs.html,ignored,
>> +documentation} in the Linux kernel.
>
> Isn't the gcspr register also available? It is also worth making it clear the
> additional registers will only be available for Linux, whereas non-Linux might
> only see the gcspr (if it is visible).

I only thought to mention gcs_features_enabled and gcs_features_locked
in the documentation because they're special. But you're right, it makes
more sense to mention GCSPR as well. I'll add this sentence for v2:

  Naturally the Guarded Control Stack pointer at EL0 is also available,
  as the @code{gcspr} register.

>> +
>>  @node x86
>>  @subsection x86
>>  
>> @@ -49655,6 +49670,42 @@ of bytes.
>>  Extra registers are allowed in this feature, but they will not affect
>>  @value{GDBN}.
>>  
>> +@subsubsection GCS registers
>> +
>> +The @samp{org.gnu.gdb.aarch64.gcs} feature is optional.  If present, it
>> +means the target supports Guarded Control Stacks and must contain the
>> +following register:
>> +
>> +@itemize @minus
>> +
>> +@item
>> +@code{gcspr}, which points to the thread's Guarded Control Stack.  It is 64
>> +bits in size and has a type of @samp{data_ptr}.
>> +
>> +@end itemize
>> +
>> +The @samp{org.gnu.gdb.aarch64.gcs.linux} feature is optional.  If present,
>> +then the @samp{org.gnu.gdb.aarch64.gcs} feature must also be present.  The
>> +@samp{org.gnu.gdb.aarch64.gcs.linux} feature represents facilities provided
>> +by the Linux kernel for GCS support and should contain the following:
>> +
>> +@itemize @minus
>> +
>> +@item
>> +@code{gcs_features_enabled} shows the features currently enabled via the
>> +prctl or ptrace system calls. It is represented as if it were a 64-bit
>
> two spaces after period.

Fixed.

>> +register with a custom flags type.
>> +
>> +@item
>> +@code{gcs_features_locked} shows the features currently locked via the
>> +prctl or ptrace system calls. It is represented as if it were a 64-bit
>> +register with a custom flags type.
>
> two spaces after period.

Fixed.

>> +
>> +@end itemize
>> +
>> +Extra registers are allowed in these features, but they will not affect
>> +@value{GDBN}.
>> +
>>  @node ARC Features
>>  @subsection ARC Features
>>  @cindex target descriptions, ARC Features
>> diff --git a/gdb/features/Makefile b/gdb/features/Makefile
>> index 2afda1ccd00f..ed20a7537ffb 100644
>> --- a/gdb/features/Makefile
>> +++ b/gdb/features/Makefile
>> @@ -203,6 +203,8 @@ FEATURE_XMLFILES = aarch64-core.xml \
>>  	aarch64-fpu.xml \
>>  	aarch64-pauth.xml \
>>  	aarch64-mte.xml \
>> +	aarch64-gcs.xml \
>> +	aarch64-gcs-linux.xml \
>>  	arc/v1-core.xml \
>>  	arc/v1-aux.xml \
>>  	arc/v2-core.xml \
>> diff --git a/gdb/features/aarch64-gcs-linux.c b/gdb/features/aarch64-gcs-linux.c
>> new file mode 100644
>> index 000000000000..6b0d25b4518c
>> --- /dev/null
>> +++ b/gdb/features/aarch64-gcs-linux.c
>> @@ -0,0 +1,21 @@
>> +/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
>> +  Original: aarch64-gcs-linux.xml */
>> +
>> +#include "gdbsupport/tdesc.h"
>> +
>> +static int
>> +create_feature_aarch64_gcs_linux (struct target_desc *result, long regnum)
>> +{
>> +  struct tdesc_feature *feature;
>> +
>> +  feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs.linux");
>> +  tdesc_type_with_fields *type_with_fields;
>> +  type_with_fields = tdesc_create_flags (feature, "features_flags", 8);
>> +  tdesc_add_flag (type_with_fields, 0, "PR_SHADOW_STACK_ENABLE");
>> +  tdesc_add_flag (type_with_fields, 1, "PR_SHADOW_STACK_WRITE");
>> +  tdesc_add_flag (type_with_fields, 2, "PR_SHADOW_STACK_PUSH");
>> +
>
> Would it be worth documenting these 3 flags in the manual?

Good idea. Will do.

>> + tdesc_create_reg (feature, "gcs_features_enabled", regnum++, 1, "system", 64,
> "features_flags");
>> + tdesc_create_reg (feature, "gcs_features_locked", regnum++, 1, "system", 64,
> "features_flags");
>> +  return regnum;
>> +}
>> diff --git a/gdb/features/aarch64-gcs-linux.xml b/gdb/features/aarch64-gcs-linux.xml
>> new file mode 100644
>> index 000000000000..8d9d2ceb9260
>> --- /dev/null
>> +++ b/gdb/features/aarch64-gcs-linux.xml
>> @@ -0,0 +1,18 @@
>> +<?xml version="1.0"?>
>> +<!-- Copyright (C) 2025 Free Software Foundation, Inc.
>> +
>> +     Copying and distribution of this file, with or without modification,
>> +     are permitted in any medium without royalty provided the copyright
>> +     notice and this notice are preserved.  -->
>> +
>> +<!DOCTYPE feature SYSTEM "gdb-target.dtd">
>> +<feature name="org.gnu.gdb.aarch64.gcs.linux">
>> +  <flags id="features_flags" size="8">
>> +    <field name="PR_SHADOW_STACK_ENABLE" start="0" end="0"/>
>> +    <field name="PR_SHADOW_STACK_WRITE" start="1" end="1"/>
>> +    <field name="PR_SHADOW_STACK_PUSH" start="2" end="2"/>
>> +  </flags>
>> +
>> + <reg name="gcs_features_enabled" bitsize="64" type="features_flags" group="system"/>
>> + <reg name="gcs_features_locked" bitsize="64" type="features_flags" group="system"/>
>> +</feature>
>> diff --git a/gdb/features/aarch64-gcs.c b/gdb/features/aarch64-gcs.c
>> new file mode 100644
>> index 000000000000..2b2caf29cc8c
>> --- /dev/null
>> +++ b/gdb/features/aarch64-gcs.c
>> @@ -0,0 +1,14 @@
>> +/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
>> +  Original: aarch64-gcs.xml */
>> +
>> +#include "gdbsupport/tdesc.h"
>> +
>> +static int
>> +create_feature_aarch64_gcs (struct target_desc *result, long regnum)
>> +{
>> +  struct tdesc_feature *feature;
>> +
>> +  feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs");
>> +  tdesc_create_reg (feature, "gcspr", regnum++, 1, "system", 64, "data_ptr");
>> +  return regnum;
>> +}
>> diff --git a/gdb/features/aarch64-gcs.xml b/gdb/features/aarch64-gcs.xml
>> new file mode 100644
>> index 000000000000..bbee5e001722
>> --- /dev/null
>> +++ b/gdb/features/aarch64-gcs.xml
>> @@ -0,0 +1,11 @@
>> +<?xml version="1.0"?>
>> +<!-- Copyright (C) 2025 Free Software Foundation, Inc.
>> +
>> +     Copying and distribution of this file, with or without modification,
>> +     are permitted in any medium without royalty provided the copyright
>> +     notice and this notice are preserved.  -->
>> +
>> +<!DOCTYPE feature SYSTEM "gdb-target.dtd">
>> +<feature name="org.gnu.gdb.aarch64.gcs">
>> +  <reg name="gcspr" bitsize="64" type="data_ptr" group="system"/>
>> +</feature>
>> diff --git a/gdbserver/linux-aarch64-low.cc b/gdbserver/linux-aarch64-low.cc
>> index 2eb3af659ad1..9feb1914dbd6 100644
>> --- a/gdbserver/linux-aarch64-low.cc
>> +++ b/gdbserver/linux-aarch64-low.cc
>> @@ -39,6 +39,7 @@
>>  
>>  #include "gdb_proc_service.h"
>>  #include "arch/aarch64.h"
>> +#include "arch/aarch64-gcs-linux.h"
>>  #include "arch/aarch64-mte-linux.h"
>>  #include "arch/aarch64-scalable-linux.h"
>>  #include "linux-aarch32-tdesc.h"
>> @@ -321,6 +322,42 @@ aarch64_store_tlsregset (struct regcache *regcache, const void
> *buf)
>>      supply_register (regcache, *regnum, tls_buf + sizeof (uint64_t));
>>  }
>>  
>> +/* Fill BUF with GCS register from the regcache.  */
>> +
>> +static void
>> +aarch64_fill_gcsregset (struct regcache *regcache, void *buf)
>> +{
>> +  struct user_gcs *regset = (struct user_gcs *) buf;
>> +  int gcspr_regnum  = find_regno (regcache->tdesc, "gcspr");
>> +  int features_enabled_regnum  = find_regno (regcache->tdesc,
>> +					     "gcs_features_enabled");
>> +  int features_locked_regnum  = find_regno (regcache->tdesc,
>> +					    "gcs_features_locked");
>> +
>> +  collect_register (regcache, gcspr_regnum, &regset->gcspr_el0);
>> +  collect_register (regcache, features_enabled_regnum,
>> +		    &regset->features_enabled);
>> +  collect_register (regcache, features_locked_regnum, &regset->features_locked);
>> +}
>> +
>> +/* Store GCS register to regcache.  */
>> +
>> +static void
>> +aarch64_store_gcsregset (struct regcache *regcache, const void *buf)
>> +{
>> +  const struct user_gcs *regset = (const struct user_gcs *) buf;
>> +  int gcspr_regnum  = find_regno (regcache->tdesc, "gcspr");
>> +  int features_enabled_regnum  = find_regno (regcache->tdesc,
>> +					     "gcs_features_enabled");
>> +  int features_locked_regnum  = find_regno (regcache->tdesc,
>> +					    "gcs_features_locked");
>> +
>> +  supply_register (regcache, gcspr_regnum, &regset->gcspr_el0);
>> +  supply_register (regcache, features_enabled_regnum,
>> +		   &regset->features_enabled);
>> +  supply_register (regcache, features_locked_regnum, &regset->features_locked);
>> +}
>> +
>>  bool
>>  aarch64_target::low_supports_breakpoints ()
>>  {
>> @@ -846,6 +883,10 @@ static struct regset_info aarch64_regsets[] =
>>    { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_TLS,
>>      0, OPTIONAL_REGS,
>>      aarch64_fill_tlsregset, aarch64_store_tlsregset },
>> +  /* Guarded Control Stack registers.  */
>> +  { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_GCS,
>> +    0, OPTIONAL_REGS,
>> +    aarch64_fill_gcsregset, aarch64_store_gcsregset },
>>    NULL_REGSET
>>  };
>>  
>> @@ -909,6 +950,10 @@ aarch64_adjust_register_sets (const struct aarch64_features
> &features)
>>  	  if (features.sme2)
>>  	    regset->size = AARCH64_SME2_ZT0_SIZE;
>>  	  break;
>> +	case NT_ARM_GCS:
>> +	  if (features.gcs_linux)
>> +	    regset->size = sizeof (struct user_gcs);
>> +	  break;
>>  	default:
>>  	  gdb_assert_not_reached ("Unknown register set found.");
>>  	}
>> @@ -940,6 +985,7 @@ aarch64_target::low_arch_setup ()
>>        /* A-profile MTE is 64-bit only.  */
>>        features.mte = linux_get_hwcap2 (pid, 8) & HWCAP2_MTE;
>>        features.tls = aarch64_tls_register_count (tid);
>> +      features.gcs = features.gcs_linux = linux_get_hwcap (pid, 8) & HWCAP_GCS;
>>  
>>        /* Scalable Matrix Extension feature and size check.  */
>>        if (linux_get_hwcap2 (pid, 8) & HWCAP2_SME)
>
> Otherwise the rest of the code looks OK to me.
>
> Thanks for the patch.
>
> Reviewed-By: Luis Machado <luis.machado@arm.com>

Thanks for your quick review!
diff mbox series

Patch

diff --git a/gdb/aarch64-linux-nat.c b/gdb/aarch64-linux-nat.c
index d7869f42e825..0796b1e47955 100644
--- a/gdb/aarch64-linux-nat.c
+++ b/gdb/aarch64-linux-nat.c
@@ -51,6 +51,7 @@ 
 #include "gdb_proc_service.h"
 #include "arch-utils.h"
 
+#include "arch/aarch64-gcs-linux.h"
 #include "arch/aarch64-mte-linux.h"
 
 #include "nat/aarch64-mte-linux-ptrace.h"
@@ -542,6 +543,67 @@  store_tlsregs_to_thread (struct regcache *regcache)
     perror_with_name (_("unable to store TLS register"));
 }
 
+/* Fill GDB's register array with the GCS register values from
+   the current thread.  */
+
+static void
+fetch_gcsregs_from_thread (struct regcache *regcache)
+{
+  aarch64_gdbarch_tdep *tdep
+    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
+
+  gdb_assert (tdep->gcs_reg_base != -1);
+  gdb_assert (tdep->gcs_linux_reg_base != -1);
+
+  struct user_gcs user_gcs;
+  struct iovec iovec;
+
+  iovec.iov_base = &user_gcs;
+  iovec.iov_len = sizeof (user_gcs);
+
+  int tid = get_ptrace_pid (regcache->ptid ());
+  if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_GCS, &iovec) != 0)
+      perror_with_name (_("unable to fetch GCS registers"));
+
+  regcache->raw_supply (tdep->gcs_reg_base, &user_gcs.gcspr_el0);
+  regcache->raw_supply (tdep->gcs_linux_reg_base, &user_gcs.features_enabled);
+  regcache->raw_supply (tdep->gcs_linux_reg_base + 1,
+			&user_gcs.features_locked);
+}
+
+/* Store to the current thread the valid GCS register set in the GDB's
+   register array.  */
+
+static void
+store_gcsregs_to_thread (struct regcache *regcache)
+{
+  aarch64_gdbarch_tdep *tdep
+    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
+
+  gdb_assert (tdep->gcs_reg_base != -1);
+  gdb_assert (tdep->gcs_linux_reg_base != -1);
+
+  if (REG_VALID != regcache->get_register_status (tdep->gcs_reg_base)
+      || REG_VALID != regcache->get_register_status (tdep->gcs_linux_reg_base)
+      || REG_VALID
+	     != regcache->get_register_status (tdep->gcs_linux_reg_base + 1))
+    return;
+
+  struct user_gcs user_gcs;
+  regcache->raw_collect (tdep->gcs_reg_base, &user_gcs.gcspr_el0);
+  regcache->raw_collect (tdep->gcs_linux_reg_base, &user_gcs.features_enabled);
+  regcache->raw_collect (tdep->gcs_linux_reg_base + 1,
+			 &user_gcs.features_locked);
+
+  struct iovec iovec;
+  iovec.iov_base = &user_gcs;
+  iovec.iov_len = sizeof (user_gcs);
+
+  int tid = get_ptrace_pid (regcache->ptid ());
+  if (ptrace (PTRACE_SETREGSET, tid, NT_ARM_GCS, &iovec) != 0)
+    perror_with_name (_("unable to store GCS registers"));
+}
+
 /* The AArch64 version of the "fetch_registers" target_ops method.  Fetch
    REGNO from the target and place the result into REGCACHE.  */
 
@@ -577,6 +639,9 @@  aarch64_fetch_registers (struct regcache *regcache, int regno)
 
       if (tdep->has_sme2 ())
 	fetch_zt_from_thread (regcache);
+
+      if (tdep->has_gcs ())
+	fetch_gcsregs_from_thread (regcache);
     }
   /* General purpose register?  */
   else if (regno < AARCH64_V0_REGNUM)
@@ -609,6 +674,11 @@  aarch64_fetch_registers (struct regcache *regcache, int regno)
 	   && regno >= tdep->tls_regnum_base
 	   && regno < tdep->tls_regnum_base + tdep->tls_register_count)
     fetch_tlsregs_from_thread (regcache);
+  /* GCS register?  */
+  else if (tdep->has_gcs ()
+	   && (regno == tdep->gcs_reg_base || regno == tdep->gcs_linux_reg_base
+	       || regno == tdep->gcs_linux_reg_base + 1))
+    fetch_gcsregs_from_thread (regcache);
 }
 
 /* A version of the "fetch_registers" target_ops method used when running
@@ -680,6 +750,9 @@  aarch64_store_registers (struct regcache *regcache, int regno)
 
       if (tdep->has_sme2 ())
 	store_zt_to_thread (regcache);
+
+      if (tdep->has_gcs ())
+	store_gcsregs_to_thread (regcache);
     }
   /* General purpose register?  */
   else if (regno < AARCH64_V0_REGNUM)
@@ -706,6 +779,11 @@  aarch64_store_registers (struct regcache *regcache, int regno)
 	   && regno >= tdep->tls_regnum_base
 	   && regno < tdep->tls_regnum_base + tdep->tls_register_count)
     store_tlsregs_to_thread (regcache);
+  /* GCS register?  */
+  else if (tdep->has_gcs ()
+	   && (regno == tdep->gcs_reg_base || regno == tdep->gcs_linux_reg_base
+	       || regno == tdep->gcs_linux_reg_base + 1))
+    store_gcsregs_to_thread (regcache);
 
   /* PAuth registers are read-only.  */
 }
@@ -881,6 +959,7 @@  aarch64_linux_nat_target::read_description ()
      active or not.  */
   features.vq = aarch64_sve_get_vq (tid);
   features.pauth = hwcap & AARCH64_HWCAP_PACA;
+  features.gcs = features.gcs_linux = hwcap & HWCAP_GCS;
   features.mte = hwcap2 & HWCAP2_MTE;
   features.tls = aarch64_tls_register_count (tid);
   /* SME feature check.  */
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index a194ac809c23..ce213bb482b9 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -50,6 +50,7 @@ 
 #include "record-full.h"
 #include "linux-record.h"
 
+#include "arch/aarch64-gcs-linux.h"
 #include "arch/aarch64-mte.h"
 #include "arch/aarch64-mte-linux.h"
 #include "arch/aarch64-scalable-linux.h"
@@ -1604,6 +1605,27 @@  aarch64_linux_iterate_over_regset_sections (struct gdbarch *gdbarch,
       cb (".reg-aarch-tls", sizeof_tls_regset, sizeof_tls_regset,
 	  &aarch64_linux_tls_regset, "TLS register", cb_data);
     }
+
+  /* Handle GCS registers.  */
+  if (tdep->has_gcs ())
+    {
+      /* Create this on the fly in order to handle the variable regnums.  */
+      const struct regcache_map_entry gcs_regmap[] =
+	{
+	  { 1, tdep->gcs_linux_reg_base, 8 },      /* features_enabled */
+	  { 1, tdep->gcs_linux_reg_base + 1, 8 },  /* features_locked */
+	  { 1, tdep->gcs_reg_base, 8 },            /* GCSPR */
+	  { 0 }
+	};
+
+      const struct regset aarch64_linux_gcs_regset =
+	{
+	  gcs_regmap, regcache_supply_regset, regcache_collect_regset
+	};
+
+      cb (".reg-aarch-gcs", sizeof (struct user_gcs), sizeof (struct user_gcs),
+	  &aarch64_linux_gcs_regset, "GCS registers", cb_data);
+    }
 }
 
 /* Implement the "core_read_description" gdbarch method.  */
@@ -1628,6 +1650,7 @@  aarch64_linux_core_read_description (struct gdbarch *gdbarch,
      length.  */
   features.vq = aarch64_linux_core_read_vq_from_sections (gdbarch, abfd);
   features.pauth = hwcap & AARCH64_HWCAP_PACA;
+  features.gcs = features.gcs_linux = hwcap & HWCAP_GCS;
   features.mte = hwcap2 & HWCAP2_MTE;
 
   /* Handle the TLS section.  */
diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 8d54e59f332a..d728f60e9e15 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -159,6 +159,18 @@  static const char *const aarch64_mte_register_names[] =
   "tag_ctl"
 };
 
+static const char *const aarch64_gcs_register_names[] = {
+  /* Guarded Control Stack Pointer Register.  */
+  "gcspr"
+};
+
+static const char *const aarch64_gcs_linux_register_names[] = {
+  /* Field in struct user_gcs.  */
+  "gcs_features_enabled",
+  /* Field in struct user_gcs.  */
+  "gcs_features_locked",
+};
+
 static int aarch64_stack_frame_destroyed_p (struct gdbarch *, CORE_ADDR);
 
 /* AArch64 prologue cache structure.  */
@@ -1875,6 +1887,39 @@  pass_in_v_vfp_candidate (struct gdbarch *gdbarch, struct regcache *regcache,
     }
 }
 
+/* Push LR_VALUE to the Guarded Control Stack.  */
+
+static void
+aarch64_push_gcs_entry (regcache *regs, CORE_ADDR lr_value)
+{
+  gdbarch *arch = regs->arch ();
+  aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (arch);
+  CORE_ADDR gcs_addr;
+
+  enum register_status status = regs->cooked_read (tdep->gcs_reg_base,
+						   &gcs_addr);
+  if (status != REG_VALID)
+    error ("Can't read $gcspr.");
+
+  gcs_addr -= 8;
+  gdb_byte buf[8];
+  store_integer (buf, gdbarch_byte_order (arch), lr_value);
+  if (target_write_memory (gcs_addr, buf, sizeof (buf)) != 0)
+    error ("Can't write to Guarded Control Stack.");
+
+  /* Update GCSPR.  */
+  regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr);
+}
+
+/* Implement the "shadow_stack_push" gdbarch method.  */
+
+static void
+aarch64_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr,
+			   regcache *regcache)
+{
+  aarch64_push_gcs_entry (regcache, new_addr);
+}
+
 /* Implement the "push_dummy_call" gdbarch method.  */
 
 static CORE_ADDR
@@ -4046,6 +4091,14 @@  aarch64_features_from_target_desc (const struct target_desc *tdesc)
   features.sme2 = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.sme2")
 		   != nullptr);
 
+  /* Check for the GCS feature.  */
+  features.gcs = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs")
+		  != nullptr);
+
+  /* Check for the GCS Linux feature.  */
+  features.gcs_linux = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux")
+			!= nullptr);
+
   return features;
 }
 
@@ -4590,6 +4643,46 @@  aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
     int first_w_regnum = num_pseudo_regs;
     num_pseudo_regs += 31;
 
+  const struct tdesc_feature *feature_gcs
+      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs");
+  int first_gcs_regnum = -1;
+  /* Add the GCS registers.  */
+  if (feature_gcs != nullptr)
+    {
+      first_gcs_regnum = num_regs;
+      /* Validate the descriptor provides the mandatory GCS registers and
+	 allocate their numbers.  */
+      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_register_names); i++)
+	valid_p &= tdesc_numbered_register (feature_gcs, tdesc_data.get (),
+					    first_gcs_regnum + i,
+					    aarch64_gcs_register_names[i]);
+
+      num_regs += i;
+    }
+
+  if (!valid_p)
+    return nullptr;
+
+  const struct tdesc_feature *feature_gcs_linux
+      = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux");
+  int first_gcs_linux_regnum = -1;
+  /* Add the GCS Linux registers.  */
+  if (feature_gcs_linux != nullptr)
+    {
+      first_gcs_linux_regnum = num_regs;
+      /* Validate the descriptor provides the mandatory GCS Linux registers
+	 and allocate their numbers.  */
+      for (i = 0; i < ARRAY_SIZE (aarch64_gcs_linux_register_names); i++)
+	valid_p &= tdesc_numbered_register (feature_gcs_linux, tdesc_data.get (),
+					    first_gcs_linux_regnum + i,
+					    aarch64_gcs_linux_register_names[i]);
+
+      /* This feature depends on the GCS feature.  */
+      valid_p &= feature_gcs != nullptr;
+
+      num_regs += i;
+    }
+
   if (!valid_p)
     return nullptr;
 
@@ -4611,6 +4704,8 @@  aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   tdep->mte_reg_base = first_mte_regnum;
   tdep->tls_regnum_base = first_tls_regnum;
   tdep->tls_register_count = tls_register_count;
+  tdep->gcs_reg_base = first_gcs_regnum;
+  tdep->gcs_linux_reg_base = first_gcs_linux_regnum;
 
   /* Set the SME register set details.  The pseudo-registers will be adjusted
      later.  */
@@ -4733,6 +4828,9 @@  aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
 
   set_gdbarch_get_pc_address_flags (gdbarch, aarch64_get_pc_address_flags);
 
+  if (tdep->has_gcs ())
+    set_gdbarch_shadow_stack_push (gdbarch, aarch64_shadow_stack_push);
+
   tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data));
 
   /* Fetch the updated number of registers after we're done adding all
@@ -4905,6 +5003,9 @@  aarch64_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file)
 	      pulongest (tdep->sme_tile_pseudo_base));
   gdb_printf (file, _("aarch64_dump_tdep: sme_svq = %s\n"),
 	      pulongest (tdep->sme_svq));
+
+  gdb_printf (file, _ ("aarch64_dump_tdep: gcs_reg_base = %d\n"),
+	      tdep->gcs_reg_base);
 }
 
 #if GDB_SELF_TEST
diff --git a/gdb/aarch64-tdep.h b/gdb/aarch64-tdep.h
index 3b8dcc26545b..0332e80da7bc 100644
--- a/gdb/aarch64-tdep.h
+++ b/gdb/aarch64-tdep.h
@@ -182,6 +182,20 @@  struct aarch64_gdbarch_tdep : gdbarch_tdep_base
   {
     return sme2_zt0_regnum > 0;
   }
+
+  /* First GCS register.  This is -1 if no GCS registers are available.  */
+  int gcs_reg_base = -1;
+
+  /* First GCS Linux-specific register.  This is -1 if no GCS Linux feature is
+     available.  */
+  int gcs_linux_reg_base = -1;
+
+  /* Returns true if the target supports GCS.  */
+  bool
+  has_gcs () const
+  {
+    return gcs_reg_base != -1;
+  }
 };
 
 const target_desc *aarch64_read_description (const aarch64_features &features);
diff --git a/gdb/arch/aarch64-gcs-linux.h b/gdb/arch/aarch64-gcs-linux.h
new file mode 100644
index 000000000000..9366caa7289a
--- /dev/null
+++ b/gdb/arch/aarch64-gcs-linux.h
@@ -0,0 +1,44 @@ 
+/* Common Linux target-dependent definitions for AArch64 GCS
+
+   Copyright (C) 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/>.  */
+
+#ifndef ARCH_AARCH64_GCS_LINUX_H
+#define ARCH_AARCH64_GCS_LINUX_H
+
+#include <stdint.h>
+
+/* Feature check for Guarded Control Stack.  */
+#ifndef HWCAP_GCS
+#define HWCAP_GCS (1UL << 32)
+#endif
+
+/* Make sure we only define these if the kernel header doesn't.  */
+#ifndef GCS_MAGIC
+
+/* GCS state (NT_ARM_GCS).  */
+
+struct user_gcs
+{
+  uint64_t features_enabled;
+  uint64_t features_locked;
+  uint64_t gcspr_el0;
+};
+
+#endif /* GCS_MAGIC */
+
+#endif /* ARCH_AARCH64_GCS_LINUX_H */
diff --git a/gdb/arch/aarch64.c b/gdb/arch/aarch64.c
index 3e1ca0547340..dff2bc16003a 100644
--- a/gdb/arch/aarch64.c
+++ b/gdb/arch/aarch64.c
@@ -26,6 +26,8 @@ 
 #include "../features/aarch64-sme.c"
 #include "../features/aarch64-sme2.c"
 #include "../features/aarch64-tls.c"
+#include "../features/aarch64-gcs.c"
+#include "../features/aarch64-gcs-linux.c"
 
 /* See arch/aarch64.h.  */
 
@@ -65,6 +67,12 @@  aarch64_create_target_description (const aarch64_features &features)
   if (features.sme2)
     regnum = create_feature_aarch64_sme2 (tdesc.get (), regnum);
 
+  if (features.gcs)
+    regnum = create_feature_aarch64_gcs (tdesc.get (), regnum);
+
+  if (features.gcs_linux)
+    regnum = create_feature_aarch64_gcs_linux (tdesc.get (), regnum);
+
   return tdesc.release ();
 }
 
diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h
index ee18b74b80f5..679d845df74e 100644
--- a/gdb/arch/aarch64.h
+++ b/gdb/arch/aarch64.h
@@ -51,6 +51,12 @@  struct aarch64_features
 
   /* Whether SME2 is supported.  */
   bool sme2 = false;
+
+  /* Whether Guarded Control Stack is supported.  */
+  bool gcs = false;
+
+  /* Whether Guarded Control Stack Linux features are supported.  */
+  bool gcs_linux = false;
 };
 
 inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs)
@@ -60,7 +66,9 @@  inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs)
     && lhs.mte == rhs.mte
     && lhs.tls == rhs.tls
     && lhs.svq == rhs.svq
-    && lhs.sme2 == rhs.sme2;
+    && lhs.sme2 == rhs.sme2
+    && lhs.gcs == rhs.gcs
+    && lhs.gcs_linux == rhs.gcs_linux;
 }
 
 namespace std
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index c7b57cf22505..03f419e90436 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -26979,6 +26979,21 @@  information automatically from the core file, and will show one of the above
 messages depending on whether the synchronous or asynchronous mode is selected.
 @xref{Memory Tagging}. @xref{Memory}.
 
+@subsubsection Guarded Control Stack
+@cindex AArch64 GCS
+@cindex AArch64 Guarded Control Stack
+
+When @value{GDBN} is debugging the AArch64 architecture, the program is
+using the feature Guarded Control Stack (GCS) and there is support in the
+kernel for GCS, @value{GDBN} will make a couple of special registers ---
+@code{gcs_features_enabled} and @code{gcs_features_locked} --- available
+through the @code{org.gnu.gdb.aarch64.gcs.linux} feature.  These registers
+expose some options that can be controlled at runtime and emulate the
+@code{prctl} option @code{PR_SET_SHADOW_STACK_STATUS}.  For further
+information, see the
+@uref{https://www.kernel.org/doc/html/latest/arch/arm64/gcs.html,ignored,
+documentation} in the Linux kernel.
+
 @node x86
 @subsection x86
 
@@ -49655,6 +49670,42 @@  of bytes.
 Extra registers are allowed in this feature, but they will not affect
 @value{GDBN}.
 
+@subsubsection GCS registers
+
+The @samp{org.gnu.gdb.aarch64.gcs} feature is optional.  If present, it
+means the target supports Guarded Control Stacks and must contain the
+following register:
+
+@itemize @minus
+
+@item
+@code{gcspr}, which points to the thread's Guarded Control Stack.  It is 64
+bits in size and has a type of @samp{data_ptr}.
+
+@end itemize
+
+The @samp{org.gnu.gdb.aarch64.gcs.linux} feature is optional.  If present,
+then the @samp{org.gnu.gdb.aarch64.gcs} feature must also be present.  The
+@samp{org.gnu.gdb.aarch64.gcs.linux} feature represents facilities provided
+by the Linux kernel for GCS support and should contain the following:
+
+@itemize @minus
+
+@item
+@code{gcs_features_enabled} shows the features currently enabled via the
+prctl or ptrace system calls. It is represented as if it were a 64-bit
+register with a custom flags type.
+
+@item
+@code{gcs_features_locked} shows the features currently locked via the
+prctl or ptrace system calls. It is represented as if it were a 64-bit
+register with a custom flags type.
+
+@end itemize
+
+Extra registers are allowed in these features, but they will not affect
+@value{GDBN}.
+
 @node ARC Features
 @subsection ARC Features
 @cindex target descriptions, ARC Features
diff --git a/gdb/features/Makefile b/gdb/features/Makefile
index 2afda1ccd00f..ed20a7537ffb 100644
--- a/gdb/features/Makefile
+++ b/gdb/features/Makefile
@@ -203,6 +203,8 @@  FEATURE_XMLFILES = aarch64-core.xml \
 	aarch64-fpu.xml \
 	aarch64-pauth.xml \
 	aarch64-mte.xml \
+	aarch64-gcs.xml \
+	aarch64-gcs-linux.xml \
 	arc/v1-core.xml \
 	arc/v1-aux.xml \
 	arc/v2-core.xml \
diff --git a/gdb/features/aarch64-gcs-linux.c b/gdb/features/aarch64-gcs-linux.c
new file mode 100644
index 000000000000..6b0d25b4518c
--- /dev/null
+++ b/gdb/features/aarch64-gcs-linux.c
@@ -0,0 +1,21 @@ 
+/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
+  Original: aarch64-gcs-linux.xml */
+
+#include "gdbsupport/tdesc.h"
+
+static int
+create_feature_aarch64_gcs_linux (struct target_desc *result, long regnum)
+{
+  struct tdesc_feature *feature;
+
+  feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs.linux");
+  tdesc_type_with_fields *type_with_fields;
+  type_with_fields = tdesc_create_flags (feature, "features_flags", 8);
+  tdesc_add_flag (type_with_fields, 0, "PR_SHADOW_STACK_ENABLE");
+  tdesc_add_flag (type_with_fields, 1, "PR_SHADOW_STACK_WRITE");
+  tdesc_add_flag (type_with_fields, 2, "PR_SHADOW_STACK_PUSH");
+
+  tdesc_create_reg (feature, "gcs_features_enabled", regnum++, 1, "system", 64, "features_flags");
+  tdesc_create_reg (feature, "gcs_features_locked", regnum++, 1, "system", 64, "features_flags");
+  return regnum;
+}
diff --git a/gdb/features/aarch64-gcs-linux.xml b/gdb/features/aarch64-gcs-linux.xml
new file mode 100644
index 000000000000..8d9d2ceb9260
--- /dev/null
+++ b/gdb/features/aarch64-gcs-linux.xml
@@ -0,0 +1,18 @@ 
+<?xml version="1.0"?>
+<!-- Copyright (C) 2025 Free Software Foundation, Inc.
+
+     Copying and distribution of this file, with or without modification,
+     are permitted in any medium without royalty provided the copyright
+     notice and this notice are preserved.  -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.aarch64.gcs.linux">
+  <flags id="features_flags" size="8">
+    <field name="PR_SHADOW_STACK_ENABLE" start="0" end="0"/>
+    <field name="PR_SHADOW_STACK_WRITE" start="1" end="1"/>
+    <field name="PR_SHADOW_STACK_PUSH" start="2" end="2"/>
+  </flags>
+
+  <reg name="gcs_features_enabled" bitsize="64" type="features_flags" group="system"/>
+  <reg name="gcs_features_locked" bitsize="64" type="features_flags" group="system"/>
+</feature>
diff --git a/gdb/features/aarch64-gcs.c b/gdb/features/aarch64-gcs.c
new file mode 100644
index 000000000000..2b2caf29cc8c
--- /dev/null
+++ b/gdb/features/aarch64-gcs.c
@@ -0,0 +1,14 @@ 
+/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
+  Original: aarch64-gcs.xml */
+
+#include "gdbsupport/tdesc.h"
+
+static int
+create_feature_aarch64_gcs (struct target_desc *result, long regnum)
+{
+  struct tdesc_feature *feature;
+
+  feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs");
+  tdesc_create_reg (feature, "gcspr", regnum++, 1, "system", 64, "data_ptr");
+  return regnum;
+}
diff --git a/gdb/features/aarch64-gcs.xml b/gdb/features/aarch64-gcs.xml
new file mode 100644
index 000000000000..bbee5e001722
--- /dev/null
+++ b/gdb/features/aarch64-gcs.xml
@@ -0,0 +1,11 @@ 
+<?xml version="1.0"?>
+<!-- Copyright (C) 2025 Free Software Foundation, Inc.
+
+     Copying and distribution of this file, with or without modification,
+     are permitted in any medium without royalty provided the copyright
+     notice and this notice are preserved.  -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.aarch64.gcs">
+  <reg name="gcspr" bitsize="64" type="data_ptr" group="system"/>
+</feature>
diff --git a/gdbserver/linux-aarch64-low.cc b/gdbserver/linux-aarch64-low.cc
index 2eb3af659ad1..9feb1914dbd6 100644
--- a/gdbserver/linux-aarch64-low.cc
+++ b/gdbserver/linux-aarch64-low.cc
@@ -39,6 +39,7 @@ 
 
 #include "gdb_proc_service.h"
 #include "arch/aarch64.h"
+#include "arch/aarch64-gcs-linux.h"
 #include "arch/aarch64-mte-linux.h"
 #include "arch/aarch64-scalable-linux.h"
 #include "linux-aarch32-tdesc.h"
@@ -321,6 +322,42 @@  aarch64_store_tlsregset (struct regcache *regcache, const void *buf)
     supply_register (regcache, *regnum, tls_buf + sizeof (uint64_t));
 }
 
+/* Fill BUF with GCS register from the regcache.  */
+
+static void
+aarch64_fill_gcsregset (struct regcache *regcache, void *buf)
+{
+  struct user_gcs *regset = (struct user_gcs *) buf;
+  int gcspr_regnum  = find_regno (regcache->tdesc, "gcspr");
+  int features_enabled_regnum  = find_regno (regcache->tdesc,
+					     "gcs_features_enabled");
+  int features_locked_regnum  = find_regno (regcache->tdesc,
+					    "gcs_features_locked");
+
+  collect_register (regcache, gcspr_regnum, &regset->gcspr_el0);
+  collect_register (regcache, features_enabled_regnum,
+		    &regset->features_enabled);
+  collect_register (regcache, features_locked_regnum, &regset->features_locked);
+}
+
+/* Store GCS register to regcache.  */
+
+static void
+aarch64_store_gcsregset (struct regcache *regcache, const void *buf)
+{
+  const struct user_gcs *regset = (const struct user_gcs *) buf;
+  int gcspr_regnum  = find_regno (regcache->tdesc, "gcspr");
+  int features_enabled_regnum  = find_regno (regcache->tdesc,
+					     "gcs_features_enabled");
+  int features_locked_regnum  = find_regno (regcache->tdesc,
+					    "gcs_features_locked");
+
+  supply_register (regcache, gcspr_regnum, &regset->gcspr_el0);
+  supply_register (regcache, features_enabled_regnum,
+		   &regset->features_enabled);
+  supply_register (regcache, features_locked_regnum, &regset->features_locked);
+}
+
 bool
 aarch64_target::low_supports_breakpoints ()
 {
@@ -846,6 +883,10 @@  static struct regset_info aarch64_regsets[] =
   { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_TLS,
     0, OPTIONAL_REGS,
     aarch64_fill_tlsregset, aarch64_store_tlsregset },
+  /* Guarded Control Stack registers.  */
+  { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_GCS,
+    0, OPTIONAL_REGS,
+    aarch64_fill_gcsregset, aarch64_store_gcsregset },
   NULL_REGSET
 };
 
@@ -909,6 +950,10 @@  aarch64_adjust_register_sets (const struct aarch64_features &features)
 	  if (features.sme2)
 	    regset->size = AARCH64_SME2_ZT0_SIZE;
 	  break;
+	case NT_ARM_GCS:
+	  if (features.gcs_linux)
+	    regset->size = sizeof (struct user_gcs);
+	  break;
 	default:
 	  gdb_assert_not_reached ("Unknown register set found.");
 	}
@@ -940,6 +985,7 @@  aarch64_target::low_arch_setup ()
       /* A-profile MTE is 64-bit only.  */
       features.mte = linux_get_hwcap2 (pid, 8) & HWCAP2_MTE;
       features.tls = aarch64_tls_register_count (tid);
+      features.gcs = features.gcs_linux = linux_get_hwcap (pid, 8) & HWCAP_GCS;
 
       /* Scalable Matrix Extension feature and size check.  */
       if (linux_get_hwcap2 (pid, 8) & HWCAP2_SME)