RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.

Schimpe, Christina
2026-04-09T12:06:58Z

> -----Original Message-----
> From: Christina Schimpe <christina.schimpe@intel.com>
> Sent: Freitag, 23. Januar 2026 09:05
> To: gdb-patches@sourceware.org
> Cc: thiago.bauermann@linaro.org
> Subject: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
>
> Until now, handling of the shadow stack pointer has been done in the target
> dependent implementations of the gdbarch hook
> 'gdbarch_shadow_stack_push'. Also amd64 and aarch64 linux specific
> unwinders for the shadow stack pointer are implemented.
> In a following patch a command line option "-shadow" will be added to the
> backtrace command to print the shadow stack backtrace. This requires more
> target-independent logic to handle the shadow stack pointer. To avoid that
> we duplicate the logic, add new source and header files "shadow-stack" for
> the implementation of shadow_stack_push and shadow stack pointer
> unwinding in a target-independent way.
> ---
> gdb/Makefile.in | 2 +
> gdb/aarch64-linux-tdep.c | 51 +-----------
> gdb/aarch64-tdep.c | 46 +++--------
> gdb/amd64-linux-tdep.c | 135 +++----------------------------
> gdb/amd64-tdep.c | 20 +++++
> gdb/gdbarch-gen.c | 126 +++++++++++++++++++++++------
> gdb/gdbarch-gen.h | 63 +++++++++++----
> gdb/gdbarch_components.py | 81 +++++++++++++++----
> gdb/infcall.c | 4 +-
> gdb/linux-tdep.c | 9 ++-
> gdb/shadow-stack.c | 166
> ++++++++++++++++++++++++++++++++++++++
> gdb/shadow-stack.h | 38 +++++++++
> 12 files changed, 473 insertions(+), 268 deletions(-) create mode 100644
> gdb/shadow-stack.c create mode 100644 gdb/shadow-stack.h
>
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in index
> 161e8128e98..7e8629048f3 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1191,6 +1191,7 @@ COMMON_SFILES = \
> sentinel-frame.c \
> ser-event.c \
> serial.c \
> + shadow-stack.c \
> skip.c \
> solib.c \
> solib-target.c \
> @@ -1652,6 +1653,7 @@ HFILES_NO_SRCDIR = \
> serial.h \
> ser-tcp.h \
> ser-unix.h \
> + shadow-stack.h \
> sh-tdep.h \
> sim-regno.h \
> skip.h \
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c index
> b85c25ecae1..f37b28067b8 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -64,6 +64,7 @@
> #include "elf/common.h"
> #include "elf/aarch64.h"
> #include "arch/aarch64-insn.h"
> +#include "shadow-stack.h"
>
> /* For std::pow */
> #include <cmath>
> @@ -2614,54 +2615,6 @@ aarch64_linux_get_shadow_stack_pointer
> (gdbarch *gdbarch, regcache *regcache,
> return gcspr;
> }
>
> -/* Implement Guarded Control Stack Pointer Register unwinding. For each
> - previous GCS pointer check if its address is still in the GCS memory
> - range. If it's outside the range set the returned value to unavailable,
> - otherwise return a value containing the new GCS pointer. */
> -
> -static value *
> -aarch64_linux_dwarf2_prev_gcspr (const frame_info_ptr &this_frame,
> - void **this_cache, int regnum)
> -{
> - value *v = frame_unwind_got_register (this_frame, regnum, regnum);
> - gdb_assert (v != nullptr);
> -
> - gdbarch *gdbarch = get_frame_arch (this_frame);
> -
> - if (v->entirely_available () && !v->optimized_out ())
> - {
> - int size = register_size (gdbarch, regnum);
> - bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> - CORE_ADDR gcspr = extract_unsigned_integer (v->contents_all ().data (),
> - size, byte_order);
> -
> - /* Starting with v6.13, the Linux kernel supports Guarded Control
> - Stack. Using /proc/PID/smaps we can only check if the current
> - GCSPR points to GCS memory. Only if this is the case a valid
> - previous GCS pointer can be calculated. */
> - std::pair<CORE_ADDR, CORE_ADDR> range;
> - if (linux_address_in_shadow_stack_mem_range (gcspr, &range))
> - {
> - /* The GCS grows downwards. To compute the previous GCS
> pointer,
> - we need to increment the GCSPR. */
> - CORE_ADDR new_gcspr = gcspr + 8;
> -
> - /* If NEW_GCSPR still points within the current GCS memory range
> - we consider it to be valid. */
> - if (new_gcspr < range.second)
> - return frame_unwind_got_address (this_frame, regnum,
> new_gcspr);
> - }
> - }
> -
> - /* Return a value which is marked as unavailable in case we could not
> - calculate a valid previous GCS pointer. */
> - value *retval
> - = value::allocate_register (get_next_frame_sentinel_okay (this_frame),
> - regnum, register_type (gdbarch, regnum));
> - retval->mark_bytes_unavailable (0, retval->type ()->length ());
> - return retval;
> -}
> -
> /* AArch64 Linux implementation of the report_signal_info gdbarch
> hook. Displays information about possible memory tag violations. */
>
> @@ -3240,7 +3193,7 @@ aarch64_linux_init_abi (struct gdbarch_info info,
> struct gdbarch *gdbarch)
> {
> set_gdbarch_get_shadow_stack_pointer (gdbarch,
>
> aarch64_linux_get_shadow_stack_pointer);
> - tdep->fn_prev_gcspr = aarch64_linux_dwarf2_prev_gcspr;
> + tdep->fn_prev_gcspr = dwarf2_prev_ssp;
> }
> }
>
> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> f1bdce453db..836af863d3a 100644
> --- a/gdb/aarch64-tdep.c
> +++ b/gdb/aarch64-tdep.c
> @@ -57,6 +57,9 @@
>
> /* For inferior_ptid and current_inferior (). */ #include "inferior.h"
> +
> +#include "shadow-stack.h"
> +
> /* For std::sqrt and std::pow. */
> #include <cmath>
>
> @@ -1889,29 +1892,6 @@ 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;
> -
> - 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); -}
> -
> /* Remove the newest entry from the Guarded Control Stack. */
>
> static void
> @@ -1929,15 +1909,6 @@ aarch64_pop_gcs_entry (regcache *regs)
> regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr + 8);
> }
>
> -/* 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
> @@ -3679,7 +3650,7 @@ aarch64_displaced_step_b (const int is_bl, const
> int32_t offset,
> gdbarch_get_shadow_stack_pointer (dsd->regs->arch (), dsd->regs,
> gcs_is_enabled);
> if (gcs_is_enabled)
> - aarch64_push_gcs_entry (dsd->regs, data->insn_addr + 4);
> + shadow_stack_push (dsd->regs, data->insn_addr + 4);
> }
> }
>
> @@ -3843,7 +3814,7 @@ aarch64_displaced_step_others (const uint32_t
> insn,
> gdbarch_get_shadow_stack_pointer (dsd->regs->arch (), dsd->regs,
> gcs_is_enabled);
> if (gcs_is_enabled)
> - aarch64_push_gcs_entry (dsd->regs, data->insn_addr + 4);
> + shadow_stack_push (dsd->regs, data->insn_addr + 4);
> }
> else
> aarch64_emit_insn (dsd->insn_buf, insn); @@ -4817,6 +4788,10 @@
> aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
> /* Register a hook for converting a memory tag to a string. */
> set_gdbarch_memtag_to_string (gdbarch, aarch64_memtag_to_string);
>
> + /* AArch64's shadow stack pointer is the GCSPR. */ if
> + (tdep->has_gcs ())
> + set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
> +
> /* ABI */
> set_gdbarch_short_bit (gdbarch, 16);
> set_gdbarch_int_bit (gdbarch, 32);
> @@ -4879,9 +4854,6 @@ 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 diff --
> git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> 77c6976e071..656daa0f0ee 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -48,8 +48,6 @@
> #include "arch/amd64-linux-tdesc.h"
> #include "inferior.h"
> #include "x86-tdep.h"
> -#include "dwarf2/frame.h"
> -#include "frame-unwind.h"
>
> /* The syscall's XML filename for i386. */ #define
> XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
> @@ -1923,18 +1921,6 @@ amd64_linux_get_tls_dtv_addr (struct gdbarch
> *gdbarch, ptid_t ptid,
> return dtv_addr;
> }
>
> -/* Return the number of bytes required to update the shadow stack pointer
> - by one element. For x32 the shadow stack elements are still 64-bit
> - aligned. Thus, gdbarch_addr_bit cannot be used to compute the new
> - stack pointer. */
> -
> -static inline int
> -amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch) -{
> - const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
> - return (binfo->bits_per_word / binfo->bits_per_byte); -}
> -
> /* Read the shadow stack pointer register and return its value, if
> possible. */
>
> @@ -1966,117 +1952,15 @@ amd64_linux_get_shadow_stack_pointer
> (gdbarch *gdbarch, regcache *regcache,
> return ssp;
> }
>
> -/* If shadow stack is enabled, push the address NEW_ADDR to the shadow
> - stack and increment the shadow stack pointer accordingly. */
> +/* Return true if ADDR points to the top of an empty shadow stack, defined
> by
> + RANGE [start_address, end_address). */
>
> -static void
> -amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR
> new_addr,
> - regcache *regcache)
> -{
> - bool shadow_stack_enabled;
> - std::optional<CORE_ADDR> ssp
> - = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache,
> - shadow_stack_enabled);
> -
> - /* For amd64/Linux, if SSP has a value that means shadow stack is
> - enabled. */
> - if (!ssp.has_value ())
> - return;
> - else
> - gdb_assert (shadow_stack_enabled);
> -
> - /* The shadow stack grows downwards. To push addresses to the stack,
> - we need to decrement SSP. */
> - const int element_size
> - = amd64_linux_shadow_stack_element_size_aligned (gdbarch);
> - const CORE_ADDR new_ssp = *ssp - element_size;
> -
> - /* Using /proc/PID/smaps we can only check if NEW_SSP points to shadow
> - stack memory. If it doesn't, we assume the stack is full. */
> - std::pair<CORE_ADDR, CORE_ADDR> memrange;
> - if (!linux_address_in_shadow_stack_mem_range (new_ssp, &memrange))
> - error (_("No space left on the shadow stack."));
> -
> - /* On x86 there can be a shadow stack token at bit 63. For x32, the
> - address size is only 32 bit. Always write back the full 8 bytes to
> - include the shadow stack token. */
> - const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> - write_memory_unsigned_integer (new_ssp, element_size, byte_order,
> - (ULONGEST) new_addr);
> -
> - i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
> - gdb_assert (tdep->ssp_regnum > -1);
> -
> - regcache_raw_write_unsigned (regcache, tdep->ssp_regnum, new_ssp); -}
> -
> -/* Implement shadow stack pointer unwinding. For each new shadow stack
> - pointer check if its address is still in the shadow stack memory range.
> - If it's outside the range set the returned value to unavailable,
> - otherwise return a value containing the new shadow stack pointer. */
> -
> -static value *
> -amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame,
> - void **this_cache, int regnum)
> -{
> - value *v = frame_unwind_got_register (this_frame, regnum, regnum);
> - gdb_assert (v != nullptr);
> -
> - gdbarch *gdbarch = get_frame_arch (this_frame);
> -
> - if (v->entirely_available () && !v->optimized_out ())
> - {
> - int size = register_size (gdbarch, regnum);
> - bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> - CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (),
> - size, byte_order);
> -
> - /* Using /proc/PID/smaps we can only check if the current shadow
> - stack pointer SSP points to shadow stack memory. Only if this is
> - the case a valid previous shadow stack pointer can be
> - calculated. */
> - std::pair<CORE_ADDR, CORE_ADDR> range;
> - if (linux_address_in_shadow_stack_mem_range (ssp, &range))
> - {
> - /* The shadow stack grows downwards. To compute the previous
> - shadow stack pointer, we need to increment SSP. */
> - CORE_ADDR new_ssp
> - = ssp + amd64_linux_shadow_stack_element_size_aligned
> (gdbarch);
> -
> - /* There can be scenarios where we have a shadow stack pointer
> - but the shadow stack is empty, as no call instruction has
> - been executed yet. If NEW_SSP points to the end of or before
> - (<=) the current shadow stack memory range we consider
> - NEW_SSP as valid (but empty). */
> - if (new_ssp <= range.second)
> - return frame_unwind_got_address (this_frame, regnum, new_ssp);
> - }
> - }
> -
> - /* Return a value which is marked as unavailable in case we could not
> - calculate a valid previous shadow stack pointer. */
> - value *retval
> - = value::allocate_register (get_next_frame_sentinel_okay (this_frame),
> - regnum, register_type (gdbarch, regnum));
> - retval->mark_bytes_unavailable (0, retval->type ()->length ());
> - return retval;
> -}
> -
> -/* Implement the "init_reg" dwarf2_frame_ops method. */
> -
> -static void
> -amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg
> *reg,
> - const frame_info_ptr &this_frame)
> +static bool
> +amd64_linux_top_addr_empty_shadow_stack
> + (gdbarch *gdbarch, const CORE_ADDR addr,
> + const std::pair<CORE_ADDR, CORE_ADDR> range)
> {
> - if (regnum == gdbarch_pc_regnum (gdbarch))
> - reg->how = DWARF2_FRAME_REG_RA;
> - else if (regnum == gdbarch_sp_regnum (gdbarch))
> - reg->how = DWARF2_FRAME_REG_CFA;
> - else if (regnum == AMD64_PL3_SSP_REGNUM)
> - {
> - reg->how = DWARF2_FRAME_REG_FN;
> - reg->loc.fn = amd64_linux_dwarf2_prev_ssp;
> - }
> + return addr == range.second;
> }
>
> static void
> @@ -2137,10 +2021,11 @@ amd64_linux_init_abi_common (struct
> gdbarch_info info, struct gdbarch *gdbarch,
> set_gdbarch_remove_non_address_bits_watchpoint
> (gdbarch, amd64_linux_remove_non_address_bits_watchpoint);
>
> - set_gdbarch_shadow_stack_push (gdbarch,
> amd64_linux_shadow_stack_push);
> set_gdbarch_get_shadow_stack_pointer (gdbarch,
>
> amd64_linux_get_shadow_stack_pointer);
> - dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
> +
> + set_gdbarch_top_addr_empty_shadow_stack
> + (gdbarch, amd64_linux_top_addr_empty_shadow_stack);
> }
>
> static void
> diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c index
> ff2e9dee117..db041618123 100755
> --- a/gdb/amd64-tdep.c
> +++ b/gdb/amd64-tdep.c
> @@ -51,6 +51,8 @@
> #include "x86-tdep.h"
> #include "amd64-ravenscar-thread.h"
> #include "gdbsupport/selftest.h"
> +#include "shadow-stack.h"
> +#include "dwarf2/frame.h"
>
> /* Note that the AMD64 architecture was previously known as x86-64.
> The latter is (forever) engraved into the canonical system name as @@ -
> 3491,6 +3493,21 @@ amd64_in_indirect_branch_thunk (struct gdbarch
> *gdbarch, CORE_ADDR pc)
> AMD64_RIP_REGNUM);
> }
>
> +static void
> +amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg
> *reg,
> + const frame_info_ptr &this_frame)
> +{
> + if (regnum == gdbarch_pc_regnum (gdbarch))
> + reg->how = DWARF2_FRAME_REG_RA;
> + else if (regnum == gdbarch_sp_regnum (gdbarch))
> + reg->how = DWARF2_FRAME_REG_CFA;
> + else if (regnum == AMD64_PL3_SSP_REGNUM)
> + {
> + reg->how = DWARF2_FRAME_REG_FN;
> + reg->loc.fn = dwarf2_prev_ssp;
> + }
> +}
> +
> void
> amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
> const target_desc *default_tdesc)
> @@ -3650,6 +3667,9 @@ amd64_init_abi (struct gdbarch_info info, struct
> gdbarch *gdbarch,
> set_gdbarch_in_indirect_branch_thunk (gdbarch,
> amd64_in_indirect_branch_thunk);
>
> + set_gdbarch_ssp_regnum (gdbarch, tdep->ssp_regnum);
> + dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
> +
> register_amd64_ravenscar_ops (gdbarch); }
>
> diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c index
> fb3d070b3c9..794bb4d50dc 100644
> --- a/gdb/gdbarch-gen.c
> +++ b/gdb/gdbarch-gen.c
> @@ -85,6 +85,7 @@ struct gdbarch
> int pc_regnum = -1;
> int ps_regnum = -1;
> int fp0_regnum = -1;
> + int ssp_regnum = -1;
> gdbarch_dwarf2_reg_to_regnum_ftype *dwarf2_reg_to_regnum =
> no_op_reg_to_regnum;
> gdbarch_register_name_ftype *register_name = nullptr;
> gdbarch_register_type_ftype *register_type = nullptr; @@ -257,8 +258,10
> @@ struct gdbarch
> gdbarch_read_core_file_mappings_ftype *read_core_file_mappings =
> default_read_core_file_mappings;
> gdbarch_use_target_description_from_corefile_notes_ftype
> *use_target_description_from_corefile_notes =
> default_use_target_description_from_corefile_notes;
> gdbarch_core_parse_exec_context_ftype *core_parse_exec_context =
> default_core_parse_exec_context;
> - gdbarch_shadow_stack_push_ftype *shadow_stack_push = nullptr;
> gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer =
> default_get_shadow_stack_pointer;
> + gdbarch_address_in_shadow_stack_memory_range_ftype
> + *address_in_shadow_stack_memory_range = nullptr;
> + gdbarch_top_addr_empty_shadow_stack_ftype
> *top_addr_empty_shadow_stack
> + = nullptr; int shadow_stack_element_size_aligned = 8;
> };
>
> /* Create a new ``struct gdbarch'' based on information provided by @@ -
> 344,6 +347,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
> /* Skip verify of pc_regnum, invalid_p == 0. */
> /* Skip verify of ps_regnum, invalid_p == 0. */
> /* Skip verify of fp0_regnum, invalid_p == 0. */
> + /* Skip verify of ssp_regnum, invalid_p == 0. */
> /* Skip verify of dwarf2_reg_to_regnum, invalid_p == 0. */
> if (gdbarch->register_name == 0)
> log.puts ("\n\tregister_name");
> @@ -527,8 +531,10 @@ verify_gdbarch (struct gdbarch *gdbarch)
> /* Skip verify of read_core_file_mappings, invalid_p == 0. */
> /* Skip verify of use_target_description_from_corefile_notes, invalid_p ==
> 0. */
> /* Skip verify of core_parse_exec_context, invalid_p == 0. */
> - /* Skip verify of shadow_stack_push, has predicate. */
> /* Skip verify of get_shadow_stack_pointer, invalid_p == 0. */
> + /* Skip verify of address_in_shadow_stack_memory_range, has
> + predicate. */
> + /* Skip verify of top_addr_empty_shadow_stack, has predicate. */
> + /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0.
> + */
> if (!log.empty ())
> internal_error (_("verify_gdbarch: the following are invalid ...%s"),
> log.c_str ());
> @@ -701,6 +707,9 @@ gdbarch_dump (struct gdbarch *gdbarch, struct
> ui_file *file)
> gdb_printf (file,
> "gdbarch_dump: fp0_regnum = %s\n",
> plongest (gdbarch->fp0_regnum));
> + gdb_printf (file,
> + "gdbarch_dump: ssp_regnum = %s\n",
> + plongest (gdbarch->ssp_regnum));
> gdb_printf (file,
> "gdbarch_dump: dwarf2_reg_to_regnum = <%s>\n",
> host_address_to_string (gdbarch->dwarf2_reg_to_regnum)); @@
> -1385,15 +1394,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct
> ui_file *file)
> gdb_printf (file,
> "gdbarch_dump: core_parse_exec_context = <%s>\n",
> host_address_to_string (gdbarch->core_parse_exec_context));
> - gdb_printf (file,
> - "gdbarch_dump: gdbarch_shadow_stack_push_p() = %d\n",
> - gdbarch_shadow_stack_push_p (gdbarch));
> - gdb_printf (file,
> - "gdbarch_dump: shadow_stack_push = <%s>\n",
> - host_address_to_string (gdbarch->shadow_stack_push));
> gdb_printf (file,
> "gdbarch_dump: get_shadow_stack_pointer = <%s>\n",
> host_address_to_string (gdbarch->get_shadow_stack_pointer));
> + gdb_printf (file,
> + "gdbarch_dump:
> gdbarch_address_in_shadow_stack_memory_range_p() = %d\n",
> + gdbarch_address_in_shadow_stack_memory_range_p (gdbarch));
> + gdb_printf (file,
> + "gdbarch_dump: address_in_shadow_stack_memory_range =
> <%s>\n",
> + host_address_to_string
> +(gdbarch->address_in_shadow_stack_memory_range));
> + gdb_printf (file,
> + "gdbarch_dump: gdbarch_top_addr_empty_shadow_stack_p() =
> %d\n",
> + gdbarch_top_addr_empty_shadow_stack_p (gdbarch));
> + gdb_printf (file,
> + "gdbarch_dump: top_addr_empty_shadow_stack = <%s>\n",
> + host_address_to_string (gdbarch-
> >top_addr_empty_shadow_stack));
> + gdb_printf (file,
> + "gdbarch_dump: shadow_stack_element_size_aligned = %s\n",
> + plongest (gdbarch->shadow_stack_element_size_aligned));
> if (gdbarch->dump_tdep != NULL)
> gdbarch->dump_tdep (gdbarch, file); } @@ -2141,6 +2159,23 @@
> set_gdbarch_fp0_regnum (struct gdbarch *gdbarch,
> gdbarch->fp0_regnum = fp0_regnum;
> }
>
> +int
> +gdbarch_ssp_regnum (struct gdbarch *gdbarch) {
> + gdb_assert (gdbarch != NULL);
> + /* Skip verify of ssp_regnum, invalid_p == 0. */
> + if (gdbarch_debug >= 2)
> + gdb_printf (gdb_stdlog, "gdbarch_ssp_regnum called\n");
> + return gdbarch->ssp_regnum;
> +}
> +
> +void
> +set_gdbarch_ssp_regnum (struct gdbarch *gdbarch,
> + int ssp_regnum)
> +{
> + gdbarch->ssp_regnum = ssp_regnum;
> +}
> +
> int
> gdbarch_dwarf2_reg_to_regnum (struct gdbarch *gdbarch, int
> dwarf2_regnr) { @@ -5455,43 +5490,84 @@
> set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch,
> gdbarch->core_parse_exec_context = core_parse_exec_context; }
>
> +std::optional<CORE_ADDR>
> +gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache
> +*regcache, bool &shadow_stack_enabled) {
> + gdb_assert (gdbarch != NULL);
> + gdb_assert (gdbarch->get_shadow_stack_pointer != NULL);
> + if (gdbarch_debug >= 2)
> + gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer
> +called\n");
> + return gdbarch->get_shadow_stack_pointer (gdbarch, regcache,
> +shadow_stack_enabled); }
> +
> +void
> +set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch,
> +
> gdbarch_get_shadow_stack_pointer_ftype
> +get_shadow_stack_pointer) {
> + gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer; }
> +
> bool
> -gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch)
> +gdbarch_address_in_shadow_stack_memory_range_p (struct gdbarch
> +*gdbarch)
> {
> gdb_assert (gdbarch != NULL);
> - return gdbarch->shadow_stack_push != NULL;
> + return gdbarch->address_in_shadow_stack_memory_range != NULL; }
> +
> +bool
> +gdbarch_address_in_shadow_stack_memory_range (struct gdbarch
> *gdbarch,
> +CORE_ADDR ADDR, std::pair<CORE_ADDR, CORE_ADDR> *range) {
> + gdb_assert (gdbarch != NULL);
> + gdb_assert (gdbarch->address_in_shadow_stack_memory_range != NULL);
> + if (gdbarch_debug >= 2)
> + gdb_printf (gdb_stdlog,
> +"gdbarch_address_in_shadow_stack_memory_range called\n");
> + return gdbarch->address_in_shadow_stack_memory_range (ADDR, range);
> }
>
> void
> -gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR
> new_addr, regcache *regcache)
> +set_gdbarch_address_in_shadow_stack_memory_range (struct gdbarch
> *gdbarch,
> +
> gdbarch_address_in_shadow_stack_memory_range_ftype
> +address_in_shadow_stack_memory_range)
> +{
> + gdbarch->address_in_shadow_stack_memory_range =
> +address_in_shadow_stack_memory_range;
> +}
> +
> +bool
> +gdbarch_top_addr_empty_shadow_stack_p (struct gdbarch *gdbarch)
> {
> gdb_assert (gdbarch != NULL);
> - gdb_assert (gdbarch->shadow_stack_push != NULL);
> + return gdbarch->top_addr_empty_shadow_stack != NULL; }
> +
> +bool
> +gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, const
> +CORE_ADDR addr, const std::pair<CORE_ADDR, CORE_ADDR> range) {
> + gdb_assert (gdbarch != NULL);
> + gdb_assert (gdbarch->top_addr_empty_shadow_stack != NULL);
> if (gdbarch_debug >= 2)
> - gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_push called\n");
> - gdbarch->shadow_stack_push (gdbarch, new_addr, regcache);
> + gdb_printf (gdb_stdlog, "gdbarch_top_addr_empty_shadow_stack
> + called\n"); return gdbarch->top_addr_empty_shadow_stack (gdbarch,
> + addr, range);
> }
>
> void
> -set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
> - gdbarch_shadow_stack_push_ftype
> shadow_stack_push)
> +set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch,
> +
> gdbarch_top_addr_empty_shadow_stack_ftype
> +top_addr_empty_shadow_stack)
> {
> - gdbarch->shadow_stack_push = shadow_stack_push;
> + gdbarch->top_addr_empty_shadow_stack =
> top_addr_empty_shadow_stack;
> }
>
> -std::optional<CORE_ADDR>
> -gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache
> *regcache, bool &shadow_stack_enabled)
> +int
> +gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch)
> {
> gdb_assert (gdbarch != NULL);
> - gdb_assert (gdbarch->get_shadow_stack_pointer != NULL);
> + /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0.
> + */
> if (gdbarch_debug >= 2)
> - gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer called\n");
> - return gdbarch->get_shadow_stack_pointer (gdbarch, regcache,
> shadow_stack_enabled);
> + gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_element_size_aligned
> + called\n"); return gdbarch->shadow_stack_element_size_aligned;
> }
>
> void
> -set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch,
> -
> gdbarch_get_shadow_stack_pointer_ftype get_shadow_stack_pointer)
> +set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch,
> + int
> shadow_stack_element_size_aligned)
> {
> - gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer;
> + gdbarch->shadow_stack_element_size_aligned =
> + shadow_stack_element_size_aligned;
> }
> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index
> 7ea9971362c..cbe970fd716 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -283,6 +283,12 @@ extern void set_gdbarch_ps_regnum (struct
> gdbarch *gdbarch, int ps_regnum); extern int gdbarch_fp0_regnum (struct
> gdbarch *gdbarch); extern void set_gdbarch_fp0_regnum (struct gdbarch
> *gdbarch, int fp0_regnum);
>
> +/* Register number for the shadow stack pointer. For inferior calls, the
> + gdbarch value ssp_regnum has to be provided. */
> +
> +extern int gdbarch_ssp_regnum (struct gdbarch *gdbarch); extern void
> +set_gdbarch_ssp_regnum (struct gdbarch *gdbarch, int ssp_regnum);
> +
> /* Provide a default mapping from a DWARF2 register number to a gdb
> REGNUM.
> Return -1 for bad REGNUM. Note: Several targets get this wrong. */
>
> @@ -1771,22 +1777,23 @@ extern void
> set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarc
> /* Some targets support special hardware-assisted control-flow protection
> technologies. For example, the Intel Control-Flow Enforcement Technology
> (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
> - To enable shadow stack support for inferior calls the shadow_stack_push
> - gdbarch hook has to be provided. The get_shadow_stack_pointer gdbarch
> - hook has to be provided to enable displaced stepping.
> -
> - Push NEW_ADDR to the shadow stack and update the shadow stack
> pointer. */
> -
> -extern bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch);
> -
> -typedef void (gdbarch_shadow_stack_push_ftype) (struct gdbarch *gdbarch,
> CORE_ADDR new_addr, regcache *regcache); -extern void
> gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR
> new_addr, regcache *regcache); -extern void
> set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
> gdbarch_shadow_stack_push_ftype *shadow_stack_push);
> -
> -/* If possible, return the shadow stack pointer. If the shadow stack
> + For GDB shadow stack support the following methods or values must be
> + provided:
> + - get_shadow_stack_pointer: required for displaced stepping and inferior
> + function calls
> + - address_in_shadow_stack_memory_range: required for shadow stack
> pointer
> + unwinding and inferior function calls
> + - top_addr_empty_shadow_stack: required for shadow stack pointer
> unwinding
> + - ssp_regnum: required for inferior function calls.
> +
> + If the shadow stack alignment is not the predefault of 8 bytes, configure
> + the gdbarch value shadow_stack_element_size_aligned.
> +
> + If possible, return the shadow stack pointer. If the shadow stack
> feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise
> set SHADOW_STACK_ENABLED to false. This hook has to be provided to
> enable
> - displaced stepping for shadow stack enabled programs.
> + displaced stepping and inferior function calls for shadow stack enabled
> + programs.
> On some architectures, the shadow stack pointer is available even if the
> feature is disabled. So dependent on the target, an implementation of
> this function may return a valid shadow stack pointer, but set @@ -1795,3
> +1802,31 @@ extern void set_gdbarch_shadow_stack_push (struct gdbarch
> *gdbarch, gdbarch_shad typedef std::optional<CORE_ADDR>
> (gdbarch_get_shadow_stack_pointer_ftype) (struct gdbarch *gdbarch,
> regcache *regcache, bool &shadow_stack_enabled); extern
> std::optional<CORE_ADDR> gdbarch_get_shadow_stack_pointer (struct
> gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
> extern void set_gdbarch_get_shadow_stack_pointer (struct gdbarch
> *gdbarch, gdbarch_get_shadow_stack_pointer_ftype
> *get_shadow_stack_pointer);
> +
> +/* Returns true if ADDR belongs to a shadow stack memory range. If this is
> + the case and RANGE is non-null, assign the shadow stack memory range to
> + RANGE [start_address, end_address). This hook has to be provided for
> + shadow stack pointer unwinding and inferior function calls. */
> +
> +extern bool gdbarch_address_in_shadow_stack_memory_range_p (struct
> +gdbarch *gdbarch);
> +
> +typedef bool (gdbarch_address_in_shadow_stack_memory_range_ftype)
> +(CORE_ADDR ADDR, std::pair<CORE_ADDR, CORE_ADDR> *range); extern
> bool
> +gdbarch_address_in_shadow_stack_memory_range (struct gdbarch
> *gdbarch,
> +CORE_ADDR ADDR, std::pair<CORE_ADDR, CORE_ADDR> *range); extern
> void
> +set_gdbarch_address_in_shadow_stack_memory_range (struct gdbarch
> +*gdbarch, gdbarch_address_in_shadow_stack_memory_range_ftype
> +*address_in_shadow_stack_memory_range);
> +
> +/* Return true if ADDR points to the top of an empty shadow stack, defined
> by
> + RANGE [start_address, end_address). This hook has to be provided to
> enable
> + unwinding of the shadow stack pointer. */
> +
> +extern bool gdbarch_top_addr_empty_shadow_stack_p (struct gdbarch
> +*gdbarch);
> +
> +typedef bool (gdbarch_top_addr_empty_shadow_stack_ftype) (struct
> +gdbarch *gdbarch, const CORE_ADDR addr, const std::pair<CORE_ADDR,
> +CORE_ADDR> range); extern bool gdbarch_top_addr_empty_shadow_stack
> +(struct gdbarch *gdbarch, const CORE_ADDR addr, const
> +std::pair<CORE_ADDR, CORE_ADDR> range); extern void
> +set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch,
> +gdbarch_top_addr_empty_shadow_stack_ftype
> +*top_addr_empty_shadow_stack);
> +
> +/* The number of bytes required to update the shadow stack pointer by one
> + element. In case the alignment is not the predefault (8 bytes), configure
> + this value. */
> +
> +extern int gdbarch_shadow_stack_element_size_aligned (struct gdbarch
> +*gdbarch); extern void set_gdbarch_shadow_stack_element_size_aligned
> +(struct gdbarch *gdbarch, int shadow_stack_element_size_aligned);
> diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
> index f419d21655e..d44e59a523d 100644
> --- a/gdb/gdbarch_components.py
> +++ b/gdb/gdbarch_components.py
> @@ -544,6 +544,17 @@ Value(
> invalid=False,
> )
>
> +Value(
> + comment="""
> +Register number for the shadow stack pointer. For inferior calls, the
> +gdbarch value ssp_regnum has to be provided.
> +""",
> + type="int",
> + name="ssp_regnum",
> + predefault="-1",
> + invalid=False,
> +)
> +
> Method(
> comment="""
> Provide a default mapping from a DWARF2 register number to a gdb
> REGNUM.
> @@ -2811,24 +2822,23 @@ Method(
> Some targets support special hardware-assisted control-flow protection
> technologies. For example, the Intel Control-Flow Enforcement Technology
> (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
> -To enable shadow stack support for inferior calls the shadow_stack_push -
> gdbarch hook has to be provided. The get_shadow_stack_pointer gdbarch -
> hook has to be provided to enable displaced stepping.
> -
> -Push NEW_ADDR to the shadow stack and update the shadow stack pointer.
> -""",
> - type="void",
> - name="shadow_stack_push",
> - params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")],
> - predicate=True,
> -)
> +For GDB shadow stack support the following methods or values must be
> +provided:
> +- get_shadow_stack_pointer: required for displaced stepping and
> +inferior
> + function calls
> +- address_in_shadow_stack_memory_range: required for shadow stack
> +pointer
> + unwinding and inferior function calls
> +- top_addr_empty_shadow_stack: required for shadow stack pointer
> +unwinding
> +- ssp_regnum: required for inferior function calls.
> +
> +If the shadow stack alignment is not the predefault of 8 bytes,
> +configure the gdbarch value shadow_stack_element_size_aligned.
>
> -Method(
> - comment="""
> If possible, return the shadow stack pointer. If the shadow stack feature is
> enabled then set SHADOW_STACK_ENABLED to true, otherwise set
> SHADOW_STACK_ENABLED to false. This hook has to be provided to enable -
> displaced stepping for shadow stack enabled programs.
> +displaced stepping and inferior function calls for shadow stack enabled
> +programs.
> On some architectures, the shadow stack pointer is available even if the
> feature is disabled. So dependent on the target, an implementation of this
> function may return a valid shadow stack pointer, but set @@ -2840,3
> +2850,46 @@ SHADOW_STACK_ENABLED to false.
> predefault="default_get_shadow_stack_pointer",
> invalid=False,
> )
> +
> +Function(
> + comment="""
> +Returns true if ADDR belongs to a shadow stack memory range. If this
> +is the case and RANGE is non-null, assign the shadow stack memory range
> +to RANGE [start_address, end_address). This hook has to be provided
> +for shadow stack pointer unwinding and inferior function calls.
> +""",
> + type="bool",
> + name="address_in_shadow_stack_memory_range",
> + params=[
> + ("CORE_ADDR", "ADDR"),
> + ("std::pair<CORE_ADDR, CORE_ADDR> *", "range")
> + ],
> + predicate=True,
> +)
> +
> +Method(
> + comment="""
> +Return true if ADDR points to the top of an empty shadow stack, defined
> +by RANGE [start_address, end_address). This hook has to be provided to
> +enable unwinding of the shadow stack pointer.
> +""",
> + type="bool",
> + name="top_addr_empty_shadow_stack",
> + params=[
> + ("const CORE_ADDR", "addr"),
> + ("const std::pair<CORE_ADDR, CORE_ADDR>", "range")
> + ],
> + predicate=True,
> +)
> +
> +Value(
> + comment="""
> +The number of bytes required to update the shadow stack pointer by one
> +element. In case the alignment is not the predefault (8 bytes),
> +configure this value.
> +""",
> + type="int",
> + name="shadow_stack_element_size_aligned",
> + predefault="8",
> + invalid=False,
> +)
> diff --git a/gdb/infcall.c b/gdb/infcall.c index dcbae679d07..e8fbc7f30d0
> 100644
> --- a/gdb/infcall.c
> +++ b/gdb/infcall.c
> @@ -42,6 +42,7 @@
> #include "thread-fsm.h"
> #include <algorithm>
> #include "gdbsupport/scope-exit.h"
> +#include "shadow-stack.h"
> #include
>
> /* True if we are debugging inferior calls. */ @@ -1464,8 +1465,7 @@
> call_function_by_hand_dummy (struct value *function,
> /* Push the return address of the inferior (bp_addr) to the shadow stack
> and update the shadow stack pointer. As we don't execute a call
> instruction to call the function we need to handle this manually. */
> - if (gdbarch_shadow_stack_push_p (gdbarch))
> - gdbarch_shadow_stack_push (gdbarch, bp_addr, regcache);
> + shadow_stack_push (regcache, bp_addr);
>
> /* Set up a frame ID for the dummy frame so we can pass it to
> set_momentary_breakpoint. We need to give the breakpoint a frame diff
> --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index ccc7fa7cf9d..9d2b32b6481
> 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -3095,8 +3095,11 @@ linux_address_in_shadow_stack_mem_range
>
> if (it != smaps.end ())
> {
> - range->first = it->start_address;
> - range->second = it->end_address;
> + if (range != nullptr)
> + {
> + range->first = it->start_address;
> + range->second = it->end_address;
> + }
> return true;
> }
>
> @@ -3146,6 +3149,8 @@ linux_init_abi (struct gdbarch_info info, struct
> gdbarch *gdbarch,
> set_gdbarch_get_siginfo_type (gdbarch, linux_get_siginfo_type);
> set_gdbarch_core_parse_exec_context (gdbarch,
> linux_corefile_parse_exec_context);
> + set_gdbarch_address_in_shadow_stack_memory_range
> + (gdbarch, linux_address_in_shadow_stack_mem_range);
> }
>
> INIT_GDB_FILE (linux_tdep)
> diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c new file mode 100644
> index 00000000000..77d7c07d970
> --- /dev/null
> +++ b/gdb/shadow-stack.c
> @@ -0,0 +1,166 @@
> +/* Manage a shadow stack pointer for GDB, the GNU debugger.
> +
> + Copyright (C) 2024-2026 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/>. */
> +
> +#include "defs.h"
> +#include "arch-utils.h"
> +#include "gdbcore.h"
> +#include "extract-store-integer.h"
> +#include "frame.h"
> +#include "frame-unwind.h"
> +#include "shadow-stack.h"
> +
> +class regcache;
> +
> +enum class ssp_update_direction
> +{
> + /* Update ssp towards the oldest (outermost) element of the shadow
> + stack. */
> + outer = 0,
> +
> + /* Update ssp towards the most recent (innermost) element of the
> + shadow stack. */
> + inner
> +};
> +
> +/* Return a new shadow stack pointer which is incremented or decremented
> + by one element dependent on DIRECTION. */
> +
> +static CORE_ADDR
> +update_shadow_stack_pointer (gdbarch *gdbarch, CORE_ADDR ssp,
> + const ssp_update_direction direction) {
> + bool increment = gdbarch_stack_grows_down (gdbarch)
> + ? direction == ssp_update_direction::outer
> + : direction == ssp_update_direction::inner;
> +
> + if (increment)
> + return ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch);
> + else
> + return ssp - gdbarch_shadow_stack_element_size_aligned (gdbarch); }
> +
> +/* See shadow-stack.h. */
> +
> +void shadow_stack_push (regcache *regcache, const CORE_ADDR
> new_addr) {
> + gdbarch *gdbarch = regcache->arch ();
> + if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
> + || gdbarch_ssp_regnum (gdbarch) == -1)
> + return;
> +
> + bool shadow_stack_enabled;
> + std::optional<CORE_ADDR> ssp
> + = gdbarch_get_shadow_stack_pointer (gdbarch, regcache,
> + shadow_stack_enabled);
> + if (!ssp.has_value () || !shadow_stack_enabled)
> + return;
> +
> + const CORE_ADDR new_ssp
> + = update_shadow_stack_pointer (gdbarch, *ssp,
> + ssp_update_direction::inner);
> +
> + /* If NEW_SSP does not point to shadow stack memory, we assume the
> + stack is full. */
> + if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> + new_ssp,
> + nullptr))
> + error (_("No space left on the shadow stack."));
> +
> + /* On x86 there can be a shadow stack token at bit 63. For x32, the
> + address size is only 32 bit. Always write back the full element
> + size to include the shadow stack token. */ const int
> + element_size
> + = gdbarch_shadow_stack_element_size_aligned (gdbarch);
> +
> + const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +
> + write_memory_unsigned_integer (new_ssp, element_size, byte_order,
> + (ULONGEST) new_addr);
> +
> + regcache_raw_write_unsigned (regcache,
> + gdbarch_ssp_regnum (gdbarch),
> + new_ssp);
> +}
> +
> +/* See shadow-stack.h. */
> +
> +value *
> +dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache,
> + int regnum)
> +{
> + value *v = frame_unwind_got_register (this_frame, regnum, regnum);
> + gdb_assert (v != nullptr);
> +
> + gdbarch *gdbarch = get_frame_arch (this_frame);
> +
> + if (gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
> + && v->entirely_available () && !v->optimized_out ())
> + {
> + const int size = register_size (gdbarch, regnum);
> + bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> + CORE_ADDR ssp = extract_unsigned_integer
> + (v->contents_all ().data (), size, byte_order);
> +
> + /* Only if the current shadow stack pointer SSP points to shadow
> + stack memory a valid previous shadow stack pointer can be
> + calculated. */
> + std::pair<CORE_ADDR, CORE_ADDR> range;
> + if (gdbarch_address_in_shadow_stack_memory_range (gdbarch, ssp,
> &range))
> + {
> + /* Note that a shadow stack memory range can change, due to
> + shadow stack switches for instance on x86 for an inter-
> + privilege far call or when calling an interrupt/exception
> + handler at a higher privilege level. Shadow stack for
> + userspace is supported for amd64 linux starting with
> + Linux kernel v6.6. However, shadow stack switches are not
> + supported due to missing kernel space support. We therefore
> + implement this unwinder without support for shadow stack
> + switches for now. */
> + const CORE_ADDR new_ssp
> + = update_shadow_stack_pointer (gdbarch, ssp,
> + ssp_update_direction::outer);
> +
> + /* On x86, if NEW_SSP points to the end outside of RANGE
> + (NEW_SSP == RANGE.SECOND), it indicates that NEW_SSP is
> + valid, but the shadow stack is empty. In contrast, for
> + ARM's Guarded Control Stack, if NEW_SSP points to the end
> + of RANGE, it means that the shadow stack feature is
> + disabled. */
> + bool is_top_addr_empty_shadow_stack
> + = gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
> + && gdbarch_top_addr_empty_shadow_stack (gdbarch, new_ssp,
> +range);
> +
> + /* Validate NEW_SSP. This may depend on both
> + IS_TOP_ADDR_EMPTY_SHADOW_STACK and the gdbarch hook
> (e.g., x86),
> + or on the hook only (e.g., ARM). */
> + if (is_top_addr_empty_shadow_stack
> + || gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> + new_ssp,
> + &range))
> + return frame_unwind_got_address (this_frame, regnum, new_ssp);
> + }
> + }
> +
> + /* Return a value which is marked as unavailable, in case we could not
> + calculate a valid previous shadow stack pointer. */
> + value *retval
> + = value::allocate_register (get_next_frame_sentinel_okay (this_frame),
> + regnum, register_type (gdbarch, regnum));
> + retval->mark_bytes_unavailable (0, retval->type ()->length ());
> + return retval;
> +}
> diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h new file mode 100644
> index 00000000000..5f8395ec047
> --- /dev/null
> +++ b/gdb/shadow-stack.h
> @@ -0,0 +1,38 @@
> +/* Definitions to manage a shadow stack pointer for GDB, the GNU
> debugger.
> +
> + Copyright (C) 2024-2026 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 GDB_SHADOW_STACK_H
> +#define GDB_SHADOW_STACK_H
> +
> +/* If shadow stack is enabled, push the address NEW_ADDR on the shadow
> + stack and update the shadow stack pointer accordingly. */
> +
> +void shadow_stack_push (regcache *regcache, const CORE_ADDR
> new_addr);
> +
> +/* Unwind the previous shadow stack pointer of THIS_FRAME's shadow
> stack
> + pointer. REGNUM is the register number of the shadow stack pointer.
> + Return a value that is unavailable in case we cannot unwind the
> + previous shadow stack pointer. Otherwise, return a value containing
> + the previous shadow stack pointer. */
> +
> +value *dwarf2_prev_ssp (const frame_info_ptr &this_frame,
> + void **this_cache, int regnum);
> +
> +#endif /* GDB_SHADOW_STACK_H */
> --
> 2.34.1

I further noticed that for the gdbarch method top_addr_empty_shadow_stack the
gdbarch parameter is unused for the aarch64 and the amd64 implementations, so we
could make it a gdbarch function.

I'll change that for the v3 if I don't hear back otherwise.

Thanks,
Christina
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928