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

Christina Schimpe
2026-01-23T08:06:55Z

[-- Attachment #1: Type: text/plain, Size: 44184 bytes --]

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

[-- Attachment #2.1: Type: text/plain, Size: 329 bytes --]

Intel Deutschland GmbH
Registered Address: Dornacher Straße 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 München HRB 186928

[-- Attachment #2.2: Type: text/html, Size: 357 bytes --]