diff --git a/gdb/aarch64-linux-nat.c b/gdb/aarch64-linux-nat.c index 725c6325ec6..89ecedda57d 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 (regcache *regcache) +{ + aarch64_gdbarch_tdep *tdep + = gdbarch_tdep (regcache->arch ()); + + gdb_assert (tdep->gcs_reg_base != -1); + gdb_assert (tdep->gcs_linux_reg_base != -1); + + user_gcs user_gcs; + 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 (regcache *regcache) +{ + aarch64_gdbarch_tdep *tdep + = 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; + + 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); + + 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_linux ()) + 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_linux () + && (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_linux ()) + 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_linux () + && (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 dd35eaff914..7d360ebceac 100644 --- a/gdb/aarch64-linux-tdep.c +++ b/gdb/aarch64-linux-tdep.c @@ -51,6 +51,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" @@ -1605,6 +1606,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_linux ()) + { + /* Create this on the fly in order to handle the variable regnums. */ + const 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 regset aarch64_linux_gcs_regset = + { + gcs_regmap, regcache_supply_regset, regcache_collect_regset + }; + + cb (".reg-aarch-gcs", sizeof (user_gcs), sizeof (user_gcs), + &aarch64_linux_gcs_regset, "GCS registers", cb_data); + } } /* Implement the "core_read_description" gdbarch method. */ @@ -1629,6 +1651,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. */ @@ -2766,6 +2789,13 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) NULL }; aarch64_gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + if (tdep->has_gcs () && !tdep->has_gcs_linux ()) + { + warning (_("Incomplete GCS support in the target: missing Linux part." + " GCS feature disabled.")); + tdep->gcs_reg_base = -1; + } + tdep->lowest_pc = 0x8000; linux_init_abi (info, gdbarch, 1); diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index 248aa3ab4da..04f3946b5ea 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,38 @@ 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 (arch); + CORE_ADDR gcs_addr; + + 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 +4090,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 +4642,48 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) int first_w_regnum = num_pseudo_regs; num_pseudo_regs += 31; + const 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 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; + } + if (!valid_p) return nullptr; @@ -4611,6 +4705,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 +4829,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 +5004,11 @@ 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); + gdb_printf (file, _("aarch64_dump_tdep: gcs_linux_reg_base = %d\n"), + tdep->gcs_linux_reg_base); } #if GDB_SELF_TEST diff --git a/gdb/aarch64-tdep.h b/gdb/aarch64-tdep.h index 3b8dcc26545..54ca641a35a 100644 --- a/gdb/aarch64-tdep.h +++ b/gdb/aarch64-tdep.h @@ -182,6 +182,27 @@ 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; + } + + /* Returns true if the target supports the Linux GCS feature. */ + bool + has_gcs_linux () const + { + return gcs_linux_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 00000000000..9366caa7289 --- /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 . */ + +#ifndef ARCH_AARCH64_GCS_LINUX_H +#define ARCH_AARCH64_GCS_LINUX_H + +#include + +/* 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 3e1ca054734..dff2bc16003 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 ee18b74b80f..679d845df74 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/features/Makefile b/gdb/features/Makefile index b206ddd5303..d17c349b6cf 100644 --- a/gdb/features/Makefile +++ b/gdb/features/Makefile @@ -204,6 +204,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 00000000000..6b0d25b4518 --- /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 00000000000..8d9d2ceb926 --- /dev/null +++ b/gdb/features/aarch64-gcs-linux.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/gdb/features/aarch64-gcs.c b/gdb/features/aarch64-gcs.c new file mode 100644 index 00000000000..2b2caf29cc8 --- /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 00000000000..bbee5e00172 --- /dev/null +++ b/gdb/features/aarch64-gcs.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.c b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c new file mode 100644 index 00000000000..77672047556 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c @@ -0,0 +1,123 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2025 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include +#include +#include +#include +#include + +/* Feature check for Guarded Control Stack. */ +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) +#endif + +#ifndef PR_GET_SHADOW_STACK_STATUS +#define PR_GET_SHADOW_STACK_STATUS 74 +#define PR_SET_SHADOW_STACK_STATUS 75 +#define PR_SHADOW_STACK_ENABLE (1UL << 0) +#endif + +/* We need to use a macro to call prctl because after GCS is enabled, it's not + possible to return from the function which enabled it. This is because the + return address of the calling function isn't on the GCS. */ +#define my_syscall2(num, arg1, arg2) \ + ({ \ + register long _num __asm__("x8") = (num); \ + register long _arg1 __asm__("x0") = (long)(arg1); \ + register long _arg2 __asm__("x1") = (long)(arg2); \ + register long _arg3 __asm__("x2") = 0; \ + register long _arg4 __asm__("x3") = 0; \ + register long _arg5 __asm__("x4") = 0; \ + \ + __asm__ volatile ("svc #0\n" \ + : "=r"(_arg1) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ + "r"(_arg5), "r"(_num) \ + : "memory", "cc"); \ + _arg1; \ + }) + +#define get_gcspr(void) \ + ({ \ + unsigned long *gcspr; \ + \ + /* Get GCSPR_EL0. */ \ + asm volatile ("mrs %0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc"); \ + \ + gcspr; \ + }) + +/* Corrupt the return address to see if GDB will report a SIGSEGV with the + expected $_siginfo.si_code. */ +static void __attribute__ ((noinline)) +function (unsigned long *gcspr) +{ + /* x30 holds the return address. */ + register long x30 __asm__("x30") __attribute__ ((unused)); + + /* Print GCSPR to stdout so that the testcase can capture it. */ + printf ("%p\n", get_gcspr ()); + fflush (stdout); + + /* Cause a GCS exception. */ + x30 = 0xbadc0ffee; + __asm__ volatile ("ret\n"); +} + +int +main (void) +{ + if (!(getauxval (AT_HWCAP) & HWCAP_GCS)) + { + fprintf (stderr, "GCS support not found in AT_HWCAP\n"); + return EXIT_FAILURE; + } + + /* Force shadow stacks on, our tests *should* be fine with or + without libc support and with or without this having ended + up tagged for GCS and enabled by the dynamic linker. We + can't use the libc prctl() function since we can't return + from enabling the stack. Also lock GCS if not already + locked so we can test behaviour when it's locked. */ + unsigned long gcs_mode; + int ret = my_syscall2 (__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to read GCS state: %d\n", ret); + return EXIT_FAILURE; + } + + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) + { + gcs_mode = PR_SHADOW_STACK_ENABLE; + ret = my_syscall2 (__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to configure GCS: %d\n", ret); + return EXIT_FAILURE; + } + } + + unsigned long *gcspr = get_gcspr (); + + /* Pass gscpr to function just so it's used for something. */ + function (gcspr); /* Break here. */ + + /* Avoid returning, in case libc doesn't understand GCS. */ + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp new file mode 100644 index 00000000000..e75e63991dd --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp @@ -0,0 +1,111 @@ +# Copyright 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Test reading and writing the core dump of a binary that uses a Guarded +# Control Stack. + +require allow_aarch64_gcs_tests + +standard_testfile + +if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } { + return +} + +set linespec ${srcfile}:[gdb_get_line_number "Break here"] + +if ![runto $linespec] { + return +} + +# Obtain an OS-generated core file. Save test program output to +# ${binfile}.out. +set core_filename [core_find $binfile {} {} "${binfile}.out"] +set core_generated [expr {$core_filename != ""}] + +# Make sure GDB can read the given core file correctly. +proc check_core_file {core_filename saved_gcspr} { + global decimal hex + + # Load the core file. + if [gdb_test "core $core_filename" \ + [multi_line \ + "Core was generated by .*\\." \ + "Program terminated with signal SIGSEGV, Segmentation fault\\." \ + "#0 function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \ + "$decimal.*__asm__ volatile \\(\"ret\\\\n\"\\);"] \ + "load core file"] { + return -1 + } + + # Check the value of GCSPR in the core file. + gdb_test "print/x \$gcspr" "\\$\[0-9\]+ = $saved_gcspr" \ + "gcspr contents from core file" +} + +if {!$core_generated} { + untested "unable to create or find corefile" +} + +if {$core_generated} { + clean_restart $binfile + + with_test_prefix "OS corefile" { + # Read GCSPR value from saved output of the test program. + set out_id [open ${binfile}.out "r"] + set gcspr_in_core [gets $out_id] + close $out_id + + check_core_file $core_filename $gcspr_in_core + } +} + +if ![gcore_cmd_available] { + unsupported "target does not support gcore command." + return +} + +clean_restart $binfile + +if ![runto $linespec] { + return +} + +# Continue until a crash. The line with the hex number is optional because +# it's printed by the test program, and doesn't appear in the Expect buffer +# when testing a remote target. +gdb_test "continue" \ + [multi_line \ + "Continuing\\." \ + "($hex\r\n)?" \ + "Program received signal SIGSEGV, Segmentation fault\\." \ + "function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \ + {.*__asm__ volatile \("ret\\n"\);}] \ + "continue to SIGSEGV" + +set gcspr_in_gcore [get_valueof "/x" "\$gcspr" "*unknown*"] + +# Generate the gcore core file. +set gcore_filename [standard_output_file "${testfile}.gcore"] +set gcore_generated [gdb_gcore_cmd "$gcore_filename" "generate gcore file"] + +gdb_assert { $gcore_generated } "gcore corefile created" +if {$gcore_generated} { + clean_restart $binfile + + with_test_prefix "gcore corefile" { + check_core_file $gcore_filename $gcspr_in_gcore + } +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-tdesc-without-linux.xml b/gdb/testsuite/gdb.arch/aarch64-gcs-tdesc-without-linux.xml new file mode 100644 index 00000000000..056ab58b82a --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-tdesc-without-linux.xml @@ -0,0 +1,65 @@ + + + + aarch64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.c b/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.c new file mode 100644 index 00000000000..10cf749f3e6 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.c @@ -0,0 +1,26 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2025 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +int +main (void) +{ + printf ("Hello, world!\n"); + + return 0; +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.exp b/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.exp new file mode 100644 index 00000000000..f0508cdc247 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.exp @@ -0,0 +1,48 @@ +# Copyright 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Test that GDB complains when given a target description with the GCS feature +# but not the GCS Linux feature. + +require allow_aarch64_gcs_tests + +standard_testfile + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { + return +} + +set xml_path "${srcdir}/${subdir}/aarch64-gcs-tdesc-without-linux.xml" + +gdb_test "set tdesc filename ${xml_path}" \ + "warning: Incomplete GCS support in the target: missing Linux part. GCS feature disabled." \ + "warn about incomplete GCS support" + +# We can't test a debugging session on a remote target because with the +# wrong tdesc, GDB expects a g packet reply with the wrong size. +if {[gdb_protocol_is_remote]} { + return +} + +if ![runto_main] { + return +} + +gdb_test "print \$gcspr" " = " "GCSPR is unavailable" + +# Now check that we can continue the debugging session normally. +gdb_test "next" + +gdb_continue_to_end diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.c b/gdb/testsuite/gdb.arch/aarch64-gcs.c new file mode 100644 index 00000000000..39519e46705 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.c @@ -0,0 +1,180 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2025 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include +#include +#include +#include +#include +#include + +/* Feature check for Guarded Control Stack. */ +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) +#endif + +#ifndef PR_GET_SHADOW_STACK_STATUS +#define PR_GET_SHADOW_STACK_STATUS 74 +#define PR_SET_SHADOW_STACK_STATUS 75 +#define PR_SHADOW_STACK_ENABLE (1UL << 0) +#endif + +/* We need to use a macro to call prctl because after GCS is enabled, it's not + possible to return from the function which enabled it. This is because the + return address of the calling function isn't on the GCS. */ +#define my_syscall2(num, arg1, arg2) \ + ({ \ + register long _num __asm__("x8") = (num); \ + register long _arg1 __asm__("x0") = (long)(arg1); \ + register long _arg2 __asm__("x1") = (long)(arg2); \ + register long _arg3 __asm__("x2") = 0; \ + register long _arg4 __asm__("x3") = 0; \ + register long _arg5 __asm__("x4") = 0; \ + \ + __asm__ volatile ("svc #0\n" \ + : "=r"(_arg1) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ + "r"(_arg5), "r"(_num) \ + : "memory", "cc"); \ + _arg1; \ + }) + +#define get_gcspr(void) \ + ({ \ + unsigned long *gcspr; \ + \ + /* Get GCSPR_EL0. */ \ + asm volatile ("mrs %0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc"); \ + \ + gcspr; \ + }) + +static unsigned long *handler_gcspr = 0; + +static void +handler (int sig) +{ + handler_gcspr = get_gcspr (); +} + +static int __attribute__ ((unused)) +called_from_gdb (int val) +{ + return val + 1; +} + +/* Corrupt the return address to see if GDB will report a SIGSEGV with the + expected $_siginfo.si_code. */ +static void __attribute__ ((noinline)) +normal_function2 (void) +{ + /* x30 holds the return address. */ + register unsigned long x30 __asm__("x30") __attribute__ ((unused)); + + /* Cause a GCS exception. */ + x30 = 0xbadc0ffee; + /* Use explicit ret so that we can verify that a SIGSEGV was generated + exactly on the return instruction. */ + __asm__ volatile ("ret\n"); +} + +static inline void __attribute__ ((__always_inline__)) +inline_function2 (void) +{ + normal_function2 (); +} + +static void __attribute__ ((noinline)) +normal_function1 (void) +{ + inline_function2 (); +} + +/* First in a sequence of inline and normal functions, to test GDB + backtrace. */ +static inline void __attribute__ ((__always_inline__)) +inline_function1 (void) +{ + normal_function1 (); +} + +/* Trivial function, just so that GDB can test return with wrong GCSPR. */ +static void __attribute__ ((noinline)) +normal_function0 (void) +{ + /* Use explicit ret so that we can verify that a SIGSEGV was generated + exactly on the return instruction. */ + __asm__ volatile ("ret\n"); +} + +int +main (void) +{ + if (!(getauxval (AT_HWCAP) & HWCAP_GCS)) + { + fprintf (stderr, "GCS support not found in AT_HWCAP\n"); + return EXIT_FAILURE; + } + + /* Force shadow stacks on, our tests *should* be fine with or + without libc support and with or without this having ended + up tagged for GCS and enabled by the dynamic linker. We + can't use the libc prctl() function since we can't return + from enabling the stack. Also lock GCS if not already + locked so we can test behaviour when it's locked. */ + unsigned long gcs_mode; + int ret = my_syscall2 (__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to read GCS state: %d\n", ret); + return EXIT_FAILURE; + } + + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) + { + gcs_mode = PR_SHADOW_STACK_ENABLE; + ret = my_syscall2 (__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to configure GCS: %d\n", ret); + return EXIT_FAILURE; + } + } + + /* Regular function call. */ + normal_function0 (); + + /* This is used by GDB. */ + __attribute__((unused)) unsigned long *gcspr = get_gcspr (); + + struct sigaction act = { 0 }; + + act.sa_handler = &handler; /* Break here. */ + if (sigaction (SIGUSR1, &act, NULL) == -1) + { + perror ("sigaction"); + exit (EXIT_FAILURE); + } + + raise (SIGUSR1); + +/* Call sequence of inline and normal functions, to test GDB backtrace. */ + inline_function1 (); + + /* Avoid returning, in case libc doesn't understand GCS. */ + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.exp b/gdb/testsuite/gdb.arch/aarch64-gcs.exp new file mode 100644 index 00000000000..3309059bfd8 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.exp @@ -0,0 +1,90 @@ +# Copyright 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Test a binary that uses a Guarded Control Stack. + +require allow_aarch64_gcs_tests + +standard_testfile + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { + return +} + +set linespec ${srcfile}:[gdb_get_line_number "Break here"] + +if ![runto ${linespec}] { + return +} + +gdb_test "print \$gcs_features_enabled" \ + [string_to_regexp { = [ PR_SHADOW_STACK_ENABLE ]}] \ + "GCS is enabled" + +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "GDB knows about gcspr" +gdb_test "print \$gcspr == gcspr" ". = 1" "GDB has the correct gcspr value" +gdb_test_no_output "set \$gcspr_in_main = \$gcspr" \ + "save gcspr value in main for later" + +# If the inferior function call fails, we don't want the tests following it +# to be affected. +gdb_test_no_output "set unwindonsignal on" +gdb_test "print called_from_gdb (41)" ". = 42" "call inferior function" + +gdb_test "break handler" "Breakpoint \[0-9\]+ .*aarch64-gcs.c, line \[0-9\]+\\." +gdb_test "handle SIGUSR1 nostop" \ + ".*\r\nSIGUSR1\\s+No\\s+Yes\\s+Yes\\s+User defined signal 1" \ + "let the inferior receive SIGUSR1 uninterrupted" +gdb_test "continue" \ + ".*\r\nBreakpoint \[0-9\]+, handler \\(sig=10\\) at .*aarch64-gcs.c.*handler_gcspr = get_gcspr \\(\\);" \ + "continue to signal handler" + +# Select the frame above the frame, which makes GDB +# unwind the gcspr from the signal frame GCS context. +gdb_test "frame 2" "#2 ($hex in )?\\S+ \\(.*\\) (at|from) \\S+.*" \ + "reached frame 2" +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "gcspr in frame level 2" + +gdb_test "continue" \ + [multi_line \ + "Continuing\\." \ + "" \ + "Program received signal SIGSEGV, Segmentation fault\\." \ + "normal_function2 \\(\\) at .*aarch64-gcs.c:$decimal" \ + "${decimal}\\s+__asm__ volatile \\(\"ret\\\\n\"\\);"] \ + "continue to SIGSEGV" + +gdb_test "print \$_siginfo.si_code" ". = 10" \ + "test value of si_code when GCS SIGSEGV happens" +# The GCS grows down, and there are two real frames until main. +gdb_test "print \$gcspr == \$gcspr_in_main - 16" ". = 1" \ + "test value of gcspr when GCS SIGSEGV happens" + +# Test writing to GCSPR. +clean_restart ${binfile} +if ![runto normal_function0] { + return +} + +gdb_test_no_output "set \$gcspr = 0xbadc0ffee" "set bogus gcspr value" +# Continue to make sure that the value was actually written to the register. +gdb_test "continue" \ + [multi_line \ + "Continuing\\." \ + "" \ + "Program received signal SIGSEGV, Segmentation fault\\." \ + "normal_function0 \\(\\) at .*aarch64-gcs.c:$decimal" \ + "${decimal}\\s+__asm__ volatile \\(\"ret\\\\n\"\\);"] \ + "continue after bad gcspr" diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index 203955d9eaa..9970af675d0 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -5295,6 +5295,57 @@ gdb_caching_proc allow_aarch64_mops_tests {} { return $allow_mops_tests } +# Run a test on the target to see if it supports AArch64 GCS extensions. +# Return 1 if so, 0 if it does not. Note this causes a restart of GDB. + +gdb_caching_proc allow_aarch64_gcs_tests {} { + global srcdir subdir gdb_prompt inferior_exited_re + + set me "allow_aarch64_gcs_tests" + + if { ![is_aarch64_target]} { + return 0 + } + + # Compile a program that tests the GCS feature. + set src { + #include + #include + + /* Feature check for Guarded Control Stack. */ + #ifndef HWCAP_GCS + #define HWCAP_GCS (1UL << 32) + #endif + + int main (void) { + bool gcs_supported = getauxval (AT_HWCAP) & HWCAP_GCS; + + /* Return success if GCS is supported. */ + return !gcs_supported; + } + } + + if {![gdb_simple_compile $me $src executable]} { + return 0 + } + + # Compilation succeeded so now run it via gdb. + set allow_gcs_tests 0 + clean_restart $obj + gdb_run_cmd + gdb_expect { + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { + verbose -log "\n$me: gcs support detected" + set allow_gcs_tests 1 + } + } + gdb_exit + remote_file build delete $obj + + verbose "$me: returning $allow_gcs_tests" 2 + return $allow_gcs_tests +} + # A helper that compiles a test case to see if __int128 is supported. proc gdb_int128_helper {lang} { return [gdb_can_simple_compile "i128-for-$lang" { diff --git a/gdbserver/linux-aarch64-low.cc b/gdbserver/linux-aarch64-low.cc index 2eb3af659ad..9d3ac803e7b 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 the GCS registers from REGCACHE. */ + +static void +aarch64_fill_gcsregset (regcache *regcache, void *buf) +{ + user_gcs *regset = (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, ®set->gcspr_el0); + collect_register (regcache, features_enabled_regnum, + ®set->features_enabled); + collect_register (regcache, features_locked_regnum, ®set->features_locked); +} + +/* Store the GCS registers in BUF to REGCACHE. */ + +static void +aarch64_store_gcsregset (regcache *regcache, const void *buf) +{ + const user_gcs *regset = (const 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, ®set->gcspr_el0); + supply_register (regcache, features_enabled_regnum, + ®set->features_enabled); + supply_register (regcache, features_locked_regnum, ®set->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 (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)