* [PATCH v4 01/13] gdb: Generalize handling of the shadow stack pointer.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 02/13] aarch64: Implement gdbarch function top_addr_empty_shadow_stack Christina Schimpe
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
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. Since the
conditions for an empty shadow stack differ betweens ARM's GCS and CET
shadow stack this further requires a new gdbarch hook
gdbarch_top_addr_empty_shadow_stack to unwind the previous shadow stack
pointer. The ARM specific implementation for
gdbarch_top_addr_empty_shadow_stack will added in the following commit:
"aarch64: Implement gdbarch function top_addr_empty_shadow_stack.".
Reviewed-By: Tom Tromey <tom@tromey.com>
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
gdb/Makefile.in | 2 +
gdb/aarch64-tdep.c | 50 +++---------
gdb/amd64-linux-tdep.c | 134 +++----------------------------
gdb/amd64-tdep.c | 22 +++++
gdb/gdbarch-gen.c | 126 +++++++++++++++++++++++------
gdb/gdbarch-gen.h | 63 +++++++++++----
gdb/gdbarch_components.py | 84 +++++++++++++++----
gdb/infcall.c | 4 +-
gdb/linux-tdep.c | 9 ++-
gdb/shadow-stack.c | 164 ++++++++++++++++++++++++++++++++++++++
gdb/shadow-stack.h | 42 ++++++++++
11 files changed, 477 insertions(+), 223 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 57f384170ab..4d1bbe5674f 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1183,6 +1183,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-tdep.c b/gdb/aarch64-tdep.c
index a84da1fd59e..848cee3043c 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>
@@ -1888,29 +1891,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
@@ -1928,15 +1908,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
@@ -3674,11 +3645,7 @@ aarch64_displaced_step_b (const int is_bl, const int32_t offset,
regcache_cooked_write_unsigned (dsd->regs, AARCH64_LR_REGNUM,
data->insn_addr + 4);
dsd->dsc->linked_branch = true;
- bool gcs_is_enabled;
- 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);
}
}
@@ -3842,7 +3809,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);
@@ -4816,6 +4783,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);
@@ -4878,9 +4849,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 9b23db72bbe..42a62eaa975 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"
#include "cli/cli-style.h"
/* The syscall's XML filename for i386. */
@@ -1921,18 +1919,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. */
@@ -1964,117 +1950,14 @@ 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
+ (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
@@ -2135,10 +2018,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 a982e610642..0972aaf93d5 100644
--- 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
@@ -3488,6 +3490,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 == gdbarch_ssp_regnum (gdbarch))
+ {
+ 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)
@@ -3647,6 +3664,11 @@ amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
set_gdbarch_in_indirect_branch_thunk (gdbarch,
amd64_in_indirect_branch_thunk);
+ if (tdep->ssp_regnum != -1)
+ 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 f424fa2a86e..022ad496026 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;
@@ -251,8 +252,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
@@ -334,6 +337,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 == nullptr)
log.puts ("\n\tregister_name");
@@ -511,8 +515,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 ());
@@ -682,6 +688,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));
@@ -1330,15 +1339,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 != nullptr)
gdbarch->dump_tdep (gdbarch, file);
}
@@ -2077,6 +2095,23 @@ set_gdbarch_fp0_regnum (struct gdbarch *gdbarch,
gdbarch->fp0_regnum = fp0_regnum;
}
+int
+gdbarch_ssp_regnum (struct gdbarch *gdbarch)
+{
+ gdb_assert (gdbarch != nullptr);
+ /* 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)
{
@@ -5246,43 +5281,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 != nullptr);
+ gdb_assert (gdbarch->get_shadow_stack_pointer != nullptr);
+ 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 != nullptr);
- return gdbarch->shadow_stack_push != nullptr;
+ return gdbarch->address_in_shadow_stack_memory_range != nullptr;
+}
+
+bool
+gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, CORE_ADDR addr, std::pair<CORE_ADDR, CORE_ADDR> *range)
+{
+ gdb_assert (gdbarch != nullptr);
+ gdb_assert (gdbarch->address_in_shadow_stack_memory_range != nullptr);
+ 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 != nullptr);
- gdb_assert (gdbarch->shadow_stack_push != nullptr);
+ return gdbarch->top_addr_empty_shadow_stack != nullptr;
+}
+
+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 != nullptr);
+ gdb_assert (gdbarch->top_addr_empty_shadow_stack != nullptr);
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 (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 != nullptr);
- gdb_assert (gdbarch->get_shadow_stack_pointer != nullptr);
+ /* 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 678b308fba5..c05564fe05f 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -286,6 +286,12 @@ void set_gdbarch_ps_regnum (struct gdbarch *gdbarch, int ps_regnum);
int gdbarch_fp0_regnum (struct gdbarch *gdbarch);
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. */
+
+int gdbarch_ssp_regnum (struct gdbarch *gdbarch);
+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. */
@@ -1728,22 +1734,23 @@ void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarch_core_
/* 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. */
-
-bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch);
-
-using gdbarch_shadow_stack_push_ftype = void (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
-void gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
-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
@@ -1752,3 +1759,31 @@ void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shadow_stac
using gdbarch_get_shadow_stack_pointer_ftype = std::optional<CORE_ADDR> (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
std::optional<CORE_ADDR> gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
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. */
+
+bool gdbarch_address_in_shadow_stack_memory_range_p (struct gdbarch *gdbarch);
+
+using gdbarch_address_in_shadow_stack_memory_range_ftype = bool (CORE_ADDR addr, std::pair<CORE_ADDR, CORE_ADDR> *range);
+bool gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, CORE_ADDR addr, std::pair<CORE_ADDR, CORE_ADDR> *range);
+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. */
+
+bool gdbarch_top_addr_empty_shadow_stack_p (struct gdbarch *gdbarch);
+
+using gdbarch_top_addr_empty_shadow_stack_ftype = bool (const CORE_ADDR addr, const std::pair<CORE_ADDR, CORE_ADDR> range);
+bool gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, const CORE_ADDR addr, const std::pair<CORE_ADDR, CORE_ADDR> range);
+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. */
+
+int gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch);
+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 b9304d3036d..531d7e212d8 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -553,6 +553,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.
@@ -2752,24 +2763,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.
+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.
-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,
-)
-
-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
@@ -2781,3 +2791,49 @@ 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,
+)
+
+Function(
+ 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,
+ # Currently unused but we wanted to keep this hook around, since
+ # x86 32-bit shadow stacks would require a 4-byte offset, for
+ # instance. But this is not supported yet by the Linux kernel
+ # as x86 shadow stack for userspace is only supported for amd64
+ # linux starting with Linux kernel v6.6.
+ unused=True,
+)
diff --git a/gdb/infcall.c b/gdb/infcall.c
index e6b24ff5310..c992b537efe 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -43,6 +43,7 @@
#include "thread-fsm.h"
#include <algorithm>
#include "gdbsupport/scope-exit.h"
+#include "shadow-stack.h"
#include <list>
#include "cli/cli-style.h"
@@ -1485,8 +1486,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 a7381677498..beffda90fdc 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -3149,8 +3149,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;
}
@@ -3200,6 +3203,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..160d30e0bfb
--- /dev/null
+++ b/gdb/shadow-stack.c
@@ -0,0 +1,164 @@
+/* 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 "arch-utils.h"
+#include "gdbcore.h"
+#include "extract-store-integer.h"
+#include "frame.h"
+#include "frame-unwind.h"
+#include "shadow-stack.h"
+
+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,
+ (unsigned long) 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..5f540ff7d62
--- /dev/null
+++ b/gdb/shadow-stack.h
@@ -0,0 +1,42 @@
+/* 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
+
+class regcache;
+class frame_info_ptr;
+struct value;
+
+/* 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
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 02/13] aarch64: Implement gdbarch function top_addr_empty_shadow_stack.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 01/13] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 03/13] gdb: Add get_main_func_start_pc to refactor frame.c:inside_main_func Christina Schimpe
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
With the implementation of that gdbarch function we can remove
the arch specific function aarch64_linux_dwarf2_prev_gcspr and
use the generalized function dwarf2_prev_ssp instead.
Co-Authored-By: Christina Schimpe <christina.schimpe@intel.com>
---
gdb/aarch64-linux-tdep.c | 51 ++--------------------------------------
gdb/aarch64-tdep.c | 28 ++++++++++++++++++++--
2 files changed, 28 insertions(+), 51 deletions(-)
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index f11eccc1bc1..b5959057972 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -66,6 +66,7 @@
#include "elf/common.h"
#include "elf/aarch64.h"
#include "arch/aarch64-insn.h"
+#include "shadow-stack.h"
/* For std::pow */
#include <cmath>
@@ -2617,54 +2618,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. */
@@ -3243,7 +3196,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 848cee3043c..d789b8569d2 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -1908,6 +1908,26 @@ aarch64_pop_gcs_entry (regcache *regs)
regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr + 8);
}
+/* Implement the "top_addr_empty_shadow_stack" gdbarch method. */
+
+static bool
+aarch64_top_addr_empty_shadow_stack
+ (const CORE_ADDR addr,
+ const std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+ gdb_assert (addr >= range.first);
+
+ /* For AArch64, addr must be strictly less than the upper address in the
+ range, but other architectures allow it to be equal to the upper
+ address when the stack is empty so GDB core works with those addresses
+ and can send them our way. */
+ gdb_assert (addr <= range.second);
+
+ /* The GCS grows down, and the oldest entry isn't an address.
+ Just the value '0'. */
+ return addr >= range.second - 8;
+}
+
/* Implement the "push_dummy_call" gdbarch method. */
static CORE_ADDR
@@ -4783,9 +4803,13 @@ 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);
+ {
+ /* AArch64's shadow stack pointer is the GCSPR. */
+ set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
+ set_gdbarch_top_addr_empty_shadow_stack
+ (gdbarch, aarch64_top_addr_empty_shadow_stack);
+ }
/* ABI */
set_gdbarch_short_bit (gdbarch, 16);
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 03/13] gdb: Add get_main_func_start_pc to refactor frame.c:inside_main_func.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 01/13] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 02/13] aarch64: Implement gdbarch function top_addr_empty_shadow_stack Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 04/13] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
Refactor frame.c:inside_main_func to use the new function
get_main_func_start_pc, which will be called in a following commmit.
---
gdb/frame.c | 37 +++++++++++++++++++++++++------------
gdb/frame.h | 6 ++++++
2 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/gdb/frame.c b/gdb/frame.c
index cefdde5ed1e..a9f2675ce3a 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -2641,27 +2641,26 @@ frame_debug_got_null_frame (const frame_info_ptr &this_frame,
}
}
-/* Is this (non-sentinel) frame in the "main"() function? */
+/* See frame.h. */
-static bool
-inside_main_func (const frame_info_ptr &this_frame)
+std::optional<CORE_ADDR>
+get_main_func_start_pc (gdbarch* gdbarch, const language lang)
{
if (current_program_space->symfile_object_file == nullptr)
- return false;
+ return {};
- CORE_ADDR sym_addr = 0;
const char *name = main_name ();
bound_minimal_symbol msymbol
= lookup_minimal_symbol (current_program_space, name,
current_program_space->symfile_object_file);
+ CORE_ADDR sym_addr = 0;
if (msymbol.minsym != nullptr)
sym_addr = msymbol.value_address ();
/* Favor a full symbol in Fortran, for the case where the Fortran main
is also called "main". */
- if (msymbol.minsym == nullptr
- || get_frame_language (this_frame) == language_fortran)
+ if (msymbol.minsym == nullptr || lang == language_fortran)
{
/* In some language (for example Fortran) there will be no minimal
symbol with the name of the main function. In this case we should
@@ -2677,16 +2676,30 @@ inside_main_func (const frame_info_ptr &this_frame)
sym_addr = block->start ();
}
else if (msymbol.minsym == nullptr)
- return false;
+ return {};
}
/* Convert any function descriptor addresses into the actual function
code address. */
- sym_addr = (gdbarch_convert_from_func_ptr_addr
- (get_frame_arch (this_frame), sym_addr,
- current_inferior ()->top_target ()));
+ return {gdbarch_convert_from_func_ptr_addr
+ (gdbarch,
+ sym_addr,
+ current_inferior ()->top_target ())};
+}
+
+/* Is this (non-sentinel) frame in the "main"() function? */
+
+static bool
+inside_main_func (const frame_info_ptr &this_frame)
+{
+ std::optional<CORE_ADDR> sym_addr
+ = get_main_func_start_pc (get_frame_arch (this_frame),
+ get_frame_language (this_frame));
+
+ if (!sym_addr.has_value ())
+ return false;
- return sym_addr == get_frame_func (this_frame);
+ return get_frame_func (this_frame) == *sym_addr;
}
/* Test whether THIS_FRAME is inside the process entry point function. */
diff --git a/gdb/frame.h b/gdb/frame.h
index f6553fb7b6d..58732bceab4 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -887,6 +887,12 @@ extern struct symbol *get_frame_function (const frame_info_ptr &);
extern CORE_ADDR get_pc_function_start (CORE_ADDR);
+/* If possible, get the start pc of the main function of the current program
+ space for the language LANG. */
+
+extern std::optional<CORE_ADDR> get_main_func_start_pc (gdbarch *gdbarch,
+ const language lang);
+
extern frame_info_ptr find_relative_frame (frame_info_ptr, int *);
/* Wrapper over print_stack_frame modifying current_uiout with UIOUT for
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 04/13] gdb: Refactor 'stack.c:print_frame'.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (2 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 03/13] gdb: Add get_main_func_start_pc to refactor frame.c:inside_main_func Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 05/13] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
Refactor the function 'stack.c:print_frame' by introducing several
small functions.
The functions will also be used in a later patch "gdb: Implement
'bt shadow' to print the shadow stack backtrace."
Approved-By: Tom Tromey <tom@tromey.com>
---
gdb/stack.c | 90 +++++++++++++++++++++++++++++++++--------------------
1 file changed, 57 insertions(+), 33 deletions(-)
diff --git a/gdb/stack.c b/gdb/stack.c
index e084976eabf..25527618a4d 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1258,6 +1258,60 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang,
return funname;
}
+/* Print the library LIB to UIOUT for the printing of frame
+ information. */
+
+static void
+print_lib (ui_out *uiout, const char *lib)
+{
+ annotate_frame_where ();
+ uiout->wrap_hint (2);
+ uiout->text (" from ");
+ uiout->field_string ("from", lib, file_name_style.style ());
+}
+
+/* Print the filenname of SAL to UIOUT for the printing of frame
+ information. */
+
+static void
+print_filename (ui_out *uiout, symtab_and_line sal)
+{
+ annotate_frame_source_begin ();
+ const char *filename_display;
+
+ filename_display = symtab_to_filename_for_display (sal.symtab);
+ uiout->wrap_hint (3);
+ uiout->text (" at ");
+ annotate_frame_source_file ();
+ uiout->field_string ("file", filename_display, file_name_style.style ());
+
+ if (uiout->is_mi_like_p ())
+ {
+ const char *fullname = symtab_to_fullname (sal.symtab);
+ uiout->field_string ("fullname", fullname);
+ }
+
+ annotate_frame_source_file_end ();
+ uiout->text (":");
+ annotate_frame_source_line ();
+ uiout->field_signed ("line", sal.line, line_number_style.style ());
+ annotate_frame_source_end ();
+}
+
+/* If available, print FUNNAME to UIOUT for the printing of frame
+ information. */
+
+static void
+print_funname (ui_out *uiout,
+ gdb::unique_xmalloc_ptr<char> const &funname)
+{
+ annotate_frame_function_name ();
+ string_file stb;
+ gdb_puts (funname ? funname.get () : "??", &stb);
+ uiout->field_stream ("func", stb, function_name_style.style ());
+ uiout->wrap_hint (3);
+}
+
static void
print_frame (struct ui_out *uiout,
const frame_print_options &fp_opts,
@@ -1303,12 +1357,8 @@ print_frame (struct ui_out *uiout,
annotate_frame_address_end ();
uiout->text (" in ");
}
- annotate_frame_function_name ();
+ print_funname (uiout, funname);
- string_file stb;
- gdb_puts (funname ? funname.get () : "??", &stb);
- uiout->field_stream ("func", stb, function_name_style.style ());
- uiout->wrap_hint (3);
annotate_frame_args ();
uiout->text (" (");
@@ -1338,28 +1388,7 @@ print_frame (struct ui_out *uiout,
}
uiout->text (")");
if (print_what != SHORT_LOCATION && sal.symtab)
- {
- const char *filename_display;
-
- filename_display = symtab_to_filename_for_display (sal.symtab);
- annotate_frame_source_begin ();
- uiout->wrap_hint (3);
- uiout->text (" at ");
- annotate_frame_source_file ();
- uiout->field_string ("file", filename_display,
- file_name_style.style ());
- if (uiout->is_mi_like_p ())
- {
- const char *fullname = symtab_to_fullname (sal.symtab);
-
- uiout->field_string ("fullname", fullname);
- }
- annotate_frame_source_file_end ();
- uiout->text (":");
- annotate_frame_source_line ();
- uiout->field_signed ("line", sal.line, line_number_style.style ());
- annotate_frame_source_end ();
- }
+ print_filename (uiout, sal);
if (print_what != SHORT_LOCATION
&& pc.has_value () && (funname == NULL || sal.symtab == NULL))
@@ -1369,12 +1398,7 @@ print_frame (struct ui_out *uiout,
get_frame_address_in_block (frame));
if (lib)
- {
- annotate_frame_where ();
- uiout->wrap_hint (2);
- uiout->text (" from ");
- uiout->field_string ("from", lib, file_name_style.style ());
- }
+ print_lib (uiout, lib);
}
if (uiout->is_mi_like_p ())
uiout->field_string ("arch",
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 05/13] gdb: Introduce 'stack.c:print_pc' function without frame argument.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (3 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 04/13] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 06/13] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
The function will be used in a later patch "gdb: Implement 'bt shadow'
to print the shadow stack backtrace.".
Approved-By: Tom Tromey <tom@tromey.com>
---
gdb/stack.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/gdb/stack.c b/gdb/stack.c
index 25527618a4d..61cd73b8367 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -921,15 +921,14 @@ print_frame_info_to_print_what (const char *print_frame_info)
print_frame_info);
}
-/* Print the PC from FRAME, plus any flags, to UIOUT. */
+/* Print the PC, plus any FLAGS (if non-empty), to UIOUT. */
static void
-print_pc (struct ui_out *uiout, struct gdbarch *gdbarch, const frame_info_ptr &frame,
+print_pc (ui_out *uiout, gdbarch *gdbarch, const std::string &flags,
CORE_ADDR pc)
{
uiout->field_core_addr ("addr", gdbarch, pc);
- std::string flags = gdbarch_get_pc_address_flags (gdbarch, frame, pc);
if (!flags.empty ())
{
uiout->text (" [");
@@ -938,6 +937,16 @@ print_pc (struct ui_out *uiout, struct gdbarch *gdbarch, const frame_info_ptr &f
}
}
+/* Print the PC from FRAME, plus any flags, to UIOUT. */
+
+static void
+print_pc (ui_out *uiout, gdbarch *gdbarch, const frame_info_ptr &frame,
+ CORE_ADDR pc)
+{
+ std::string flags = gdbarch_get_pc_address_flags (gdbarch, frame, pc);
+ print_pc (uiout, gdbarch, flags, pc);
+}
+
/* See stack.h. */
void
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 06/13] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (4 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 05/13] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 07/13] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
To avoid code duplication, create a new function 'stack.c:find_symbol_funname',
which will be used in 'stack.c:find_frame_funname'. The function will also be
used in a following commit.
Approved-By: Tom Tromey <tom@tromey.com>
---
gdb/stack.c | 44 ++++++++++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/gdb/stack.c b/gdb/stack.c
index 61cd73b8367..f5196c384fd 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1211,6 +1211,33 @@ get_last_displayed_sal ()
return sal;
}
+/* Find the function name for the symbol SYM. */
+
+static gdb::unique_xmalloc_ptr<char>
+find_symbol_funname (const symbol *sym)
+{
+ gdb::unique_xmalloc_ptr<char> funname;
+ const char *print_name = sym->print_name ();
+
+ if (sym->language () == language_cplus)
+ {
+ /* It seems appropriate to use print_name () here,
+ to display the demangled name that we already have
+ stored in the symbol table, but we stored a version
+ with DMGL_PARAMS turned on, and here we don't want to
+ display parameters. So remove the parameters. */
+ funname = cp_remove_params (print_name);
+ }
+
+ if (funname == nullptr)
+ {
+ /* If we didn't hit the C++ case above, set *funname here. */
+ funname = make_unique_xstrdup (print_name);
+ }
+
+ return funname;
+}
+
/* Attempt to obtain the name, FUNLANG and optionally FUNCP of the function
corresponding to FRAME. */
@@ -1229,25 +1256,10 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang,
func = get_frame_function (frame);
if (func)
{
- const char *print_name = func->print_name ();
-
*funlang = func->language ();
if (funcp)
*funcp = func;
- if (*funlang == language_cplus)
- {
- /* It seems appropriate to use print_name() here,
- to display the demangled name that we already have
- stored in the symbol table, but we stored a version
- with DMGL_PARAMS turned on, and here we don't want to
- display parameters. So remove the parameters. */
- funname = cp_remove_params (print_name);
- }
-
- /* If we didn't hit the C++ case above, set *funname
- here. */
- if (funname == NULL)
- funname = make_unique_xstrdup (print_name);
+ funname = find_symbol_funname (func);
}
else
{
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 07/13] gdb: Refactor 'stack.c:print_frame_info'.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (5 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 06/13] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 08/13] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
This patch restructures the 'stack.c:print_frame_info' function by
introducing two new functions in stack.c. The new functions
will also be called in a later patch "gdb: Implement 'bt shadow'
subcommand to print the shadow stack backtrace."
Approved-By: Tom Tromey <tom@tromey.com>
---
gdb/stack.c | 159 +++++++++++++++++++++++++++++++---------------------
1 file changed, 94 insertions(+), 65 deletions(-)
diff --git a/gdb/stack.c b/gdb/stack.c
index f5196c384fd..7df1d6e33da 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -957,6 +957,86 @@ get_user_print_what_frame_info (std::optional<enum print_what> *what)
(user_frame_print_options.print_frame_info);
}
+/* Return true if PRINT_WHAT is configured to print the location of a
+ frame. */
+
+static bool
+should_print_location (print_what print_what)
+{
+ return (print_what == LOCATION
+ || print_what == SRC_AND_LOC
+ || print_what == LOC_AND_ADDRESS
+ || print_what == SHORT_LOCATION);
+}
+
+/* Print the source information for PC and SAL to UIOUT. Based on the
+ user-defined configuration disassemble-next-line, display disassembly
+ of the next source line, in addition to displaying the source line
+ itself. Print annotations describing source file and and line number
+ based on MID_STATEMENT information. If SHOW_ADDRESS is true, print the
+ program counter PC including, if non-empty, PC_ADDRESS_FLAGS. */
+
+static void
+print_source (ui_out *uiout, gdbarch *gdbarch, CORE_ADDR pc,
+ symtab_and_line sal, bool show_address, int mid_statement,
+ const std::string &pc_address_flags)
+{
+ if (sal.symtab == nullptr)
+ {
+ /* If disassemble-next-line is set to auto or on and doesn't have
+ the line debug messages for $pc, output the next instruction. */
+ if (disassemble_next_line == AUTO_BOOLEAN_AUTO
+ || disassemble_next_line == AUTO_BOOLEAN_TRUE)
+ do_gdb_disassembly (gdbarch, 1, pc, pc + 1);
+
+ /* If we do not have symtab information, we cannot print any source
+ line and must return here. */
+ return;
+ }
+
+ if (annotation_level > 0
+ && annotate_source_line (sal.symtab, sal.line, mid_statement, pc))
+ {
+ /* The call to ANNOTATE_SOURCE_LINE already printed the annotation
+ for this source line, so we avoid the two cases below and do not
+ print the actual source line. The documentation for annotations
+ makes it clear that the source line annotation is printed
+ __instead__ of printing the source line, not as well as.
+
+ However, if we fail to print the source line, which usually means
+ either the source file is missing, or the requested line is out
+ of range of the file, then we don't print the source annotation,
+ and will pass through the "normal" print source line code below,
+ the expectation is that this code will print an appropriate
+ error. */
+ }
+ else if (deprecated_print_frame_info_listing_hook)
+ deprecated_print_frame_info_listing_hook (sal.symtab, sal.line,
+ sal.line + 1, 0);
+ else
+ {
+ /* We used to do this earlier, but that is clearly wrong. This
+ function is used by many different parts of gdb, including
+ normal_stop in infrun.c, which uses this to print out the current
+ PC when we stepi/nexti into the middle of a source line. Only
+ the command line really wants this behavior. Other UIs probably
+ would like the ability to decide for themselves if it is
+ desired. */
+ if (show_address)
+ {
+ print_pc (uiout, gdbarch, pc_address_flags, pc);
+ uiout->text ("\t");
+ }
+
+ print_source_lines (sal.symtab, sal.line, sal.line + 1, 0);
+ }
+
+ /* If disassemble-next-line is set to on and there is line debug
+ messages, output assembly codes for next line. */
+ if (disassemble_next_line == AUTO_BOOLEAN_TRUE)
+ do_gdb_disassembly (gdbarch, -1, sal.pc, sal.end);
+}
+
/* Print information about frame FRAME. The output is format according
to PRINT_LEVEL and PRINT_WHAT and PRINT_ARGS. For the meaning of
PRINT_WHAT, see enum print_what comments in frame.h.
@@ -973,8 +1053,6 @@ do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts,
int set_current_sal)
{
struct gdbarch *gdbarch = get_frame_arch (frame);
- int source_print;
- int location_print;
if (!current_uiout->is_mi_like_p ()
&& fp_opts.print_frame_info != print_frame_info_auto)
@@ -1045,75 +1123,26 @@ do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts,
to get the line containing FRAME->pc. */
symtab_and_line sal = find_frame_sal (frame);
- location_print = (print_what == LOCATION
- || print_what == SRC_AND_LOC
- || print_what == LOC_AND_ADDRESS
- || print_what == SHORT_LOCATION);
- if (location_print || !sal.symtab)
- print_frame (uiout, fp_opts, frame, print_level,
- print_what, print_args, sal);
-
- source_print = (print_what == SRC_LINE || print_what == SRC_AND_LOC);
+ if (should_print_location (print_what) || sal.symtab == nullptr)
+ print_frame (uiout, fp_opts, frame, print_level, print_what,
+ print_args, sal);
- /* If disassemble-next-line is set to auto or on and doesn't have
- the line debug messages for $pc, output the next instruction. */
- if ((disassemble_next_line == AUTO_BOOLEAN_AUTO
- || disassemble_next_line == AUTO_BOOLEAN_TRUE)
- && source_print && !sal.symtab)
- do_gdb_disassembly (get_frame_arch (frame), 1,
- get_frame_pc (frame), get_frame_pc (frame) + 1);
-
- if (source_print && sal.symtab)
+ if (print_what == SRC_LINE || print_what == SRC_AND_LOC)
{
int mid_statement = ((print_what == SRC_LINE)
&& frame_show_address (frame, sal));
- if (annotation_level > 0
- && annotate_source_line (sal.symtab, sal.line, mid_statement,
- get_frame_pc (frame)))
- {
- /* The call to ANNOTATE_SOURCE_LINE already printed the
- annotation for this source line, so we avoid the two cases
- below and do not print the actual source line. The
- documentation for annotations makes it clear that the source
- line annotation is printed __instead__ of printing the source
- line, not as well as.
-
- However, if we fail to print the source line, which usually
- means either the source file is missing, or the requested
- line is out of range of the file, then we don't print the
- source annotation, and will pass through the "normal" print
- source line code below, the expectation is that this code
- will print an appropriate error. */
- }
- else if (deprecated_print_frame_info_listing_hook)
- deprecated_print_frame_info_listing_hook (sal.symtab, sal.line,
- sal.line + 1, 0);
- else
- {
- struct value_print_options opts;
-
- get_user_print_options (&opts);
- /* We used to do this earlier, but that is clearly
- wrong. This function is used by many different
- parts of gdb, including normal_stop in infrun.c,
- which uses this to print out the current PC
- when we stepi/nexti into the middle of a source
- line. Only the command line really wants this
- behavior. Other UIs probably would like the
- ability to decide for themselves if it is desired. */
- if (opts.addressprint && mid_statement)
- {
- print_pc (uiout, gdbarch, frame, get_frame_pc (frame));
- uiout->text ("\t");
- }
- print_source_lines (sal.symtab, sal.line, sal.line + 1, 0);
- }
+ value_print_options opts;
+ get_user_print_options (&opts);
+
+ const bool print_address = opts.addressprint && mid_statement;
+
+ std::string pc_address_flags
+ = gdbarch_get_pc_address_flags (gdbarch, frame, get_frame_pc (frame));
+
+ print_source (uiout, gdbarch, get_frame_pc (frame), sal,
+ print_address, mid_statement, pc_address_flags);
- /* If disassemble-next-line is set to on and there is line debug
- messages, output assembly codes for next line. */
- if (disassemble_next_line == AUTO_BOOLEAN_TRUE)
- do_gdb_disassembly (get_frame_arch (frame), -1, sal.pc, sal.end);
}
if (set_current_sal)
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 08/13] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (6 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 07/13] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 09/13] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
Add command option '-shadow" to the backtrace command to print the shadow
stack backtrace instead of the normal backtrace.
This option may be combined with '-frame-info' and implies '-no-filters'
and '-frame-arguments none'. Also the options @samp{-past-entry} or
@samp{-hide} are not supported.
This is an example for the output of 'bt -shadow' on amd64 linux:
~~
(gdb) bt -shadow
/#0 0x000055555555514a in call1 at amd64-shadow-stack.c:27
/#1 0x000055555555515f in main at amd64-shadow-stack.c:38
~~
Note that the normal backtrace includes one additional frame, since
the shadow stack backtrace relies on return addresses of the shadow
stack only. But except the missing frame arguments and frame #0 of
the normal backtrace, the backtrace is the same.
~~
(gdb) bt
/#0 call2 () at amd64-shadow-stack.c:21
/#1 0x000055555555514a in call1 () at amd64-shadow-stack.c:27
/#2 0x000055555555515f in main () at amd64-shadow-stack.c:38
~~
This commit also adds a test for 'bt -shadow' on amd64.
Although the test is OS independent we can only test this on linux,
as GDB does not support shadow stack on other OS for now.
Also we do not add a test for 32 bit, as support for shadow stack is
limited to 64 bit by the linux kernel.
---
gdb/NEWS | 3 +
gdb/doc/gdb.texinfo | 25 +
gdb/gdbarch-gen.h | 2 +
gdb/gdbarch.h | 1 +
gdb/gdbarch_components.py | 2 +
gdb/shadow-stack.c | 475 ++++++++++++++++++
gdb/shadow-stack.h | 65 +++
gdb/stack.c | 49 +-
gdb/stack.h | 43 ++
.../gdb.arch/amd64-shadow-stack-cmds.exp | 47 ++
gdb/testsuite/gdb.base/options.exp | 2 +-
11 files changed, 689 insertions(+), 25 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index ec9b5a33787..fa06d21ddac 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
*** Changes since GDB 17
+* New "-shadow" command line option for the backtrace command to print
+ the shadow stack backtrace instead of the normal backtrace.
+
* Support for the Common Trace Format (CTF) has been removed. GDB now
saves trace information exclusively in its own "tfile" format.
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index a698b2b8451..29ad2d24ca6 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8788,6 +8788,29 @@ Related setting: @ref{set print raw-frame-arguments}.
@item -frame-info @code{auto}|@code{source-line}|@code{location}|@code{source-and-location}|@code{location-and-address}|@code{short-location}
Set printing of frame information.
Related setting: @ref{set print frame-info}.
+
+@item -shadow
+Print the shadow stack backtrace instead of the normal backtrace.
+Similar to the normal backtrace @value{GDBN} prints one line per
+element (so-called shadow stack frames) on the shadow stack.
+
+A shadow stack is supported, for instance, with the Intel Control-Flow
+Enforcement Technology (@pxref{CET}) on x86 and the Guarded Control Stack
+feature (@pxref{GCS}) on AArch64.
+
+This option may be combined with @code{-frame-info} and implies
+@code{-no-filters} and @code{-frame-arguments none}. Also the options
+@samp{-past-entry} or @samp{-hide} are not supported.
+
+This is how a shadow stack backtrace looks like on amd64:
+@smallexample
+@group
+(gdb) bt -shadow
+#0 0x000055555555514a in call1 at amd64-shadow-stack.c:27
+#1 0x000055555555515f in main at amd64-shadow-stack.c:38
+@end group
+@end smallexample
+
@end table
The optional @var{qualifier} is maintained for backward compatibility.
@@ -27219,6 +27242,7 @@ information automatically from the core file, and will show one of the above
messages depending on whether the synchronous or asynchronous mode is selected.
@xref{Memory Tagging}. @xref{Memory}.
+@anchor{GCS}
@subsubsection AArch64 Guarded Control Stack
@cindex Guarded Control Stack, AArch64
@cindex GCS, AArch64
@@ -27307,6 +27331,7 @@ registers
@end itemize
+@anchor{CET}
@subsubsection Intel Control-Flow Enforcement Technology.
@cindex Intel Control-Flow Enforcement Technology.
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index c05564fe05f..e4f9bc21b1d 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1745,6 +1745,8 @@ void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarch_core_
If the shadow stack alignment is not the predefault of 8 bytes, configure
the gdbarch value shadow_stack_element_size_aligned.
+ To support the command line option 'backtrace -shadow' each value and hook
+ listed above has to be provided.
If possible, return the shadow stack pointer. If the shadow stack
feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 7077f9262e9..b587a862e90 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -32,6 +32,7 @@
#include "registry.h"
#include "solib.h"
#include "find-memory-region.h"
+#include "shadow-stack.h"
struct floatformat;
struct ui_file;
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index 531d7e212d8..9d6fdc5f941 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2774,6 +2774,8 @@ provided:
If the shadow stack alignment is not the predefault of 8 bytes, configure
the gdbarch value shadow_stack_element_size_aligned.
+To support the command line option 'backtrace -shadow' each value and hook
+listed above has to be provided.
If possible, return the shadow stack pointer. If the shadow stack
feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 160d30e0bfb..49b831617ab 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -22,6 +22,12 @@
#include "frame.h"
#include "frame-unwind.h"
#include "shadow-stack.h"
+#include "stack.h"
+#include "solib.h"
+#include "event-top.h"
+#include "cli/cli-style.h"
+#include "buffered-streams.h"
+#include "inferior.h"
enum class ssp_update_direction
{
@@ -162,3 +168,472 @@ dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache,
retval->mark_bytes_unavailable (0, retval->type ()->length ());
return retval;
}
+
+/* Return true, if PC is in the middle of a statement. Note that in the
+ middle of a statement PC range includes sal.end (SAL.PC, SAL.END].
+ Return false, if
+ - SAL.IS_STMT is false
+ - there is no location information associated with this SAL, which
+ could happen in case of inlined functions
+ - PC is not in the range (SAL.PC, SAL.END].
+ This function is similar to stack.c:frame_show_address but is used
+ to determine if we are in the middle of a statement only, not to decide
+ if we should print a frame's address. */
+
+static bool
+pc_in_middle_of_statement (CORE_ADDR pc, symtab_and_line sal)
+{
+ if (sal.is_stmt == false)
+ return false;
+
+ /* If there is a line number, but no PC, then there is no location
+ information associated with this sal. The only way that should
+ happen is for the call sites of inlined functions (SAL comes from
+ find_sal_for_pc). Otherwise, we would have some PC range if the SAL
+ came from a line table. However, as we don't have a frame for this
+ function we cannot assert (in contrast to frame_show_address). */
+ if (sal.line != 0 && sal.pc == 0 && sal.end == 0)
+ return false;
+
+ return pc > sal.pc && pc <= sal.end;
+}
+
+/* Attempt to obtain the function name based on the symbol of PC. */
+
+static gdb::unique_xmalloc_ptr<char>
+find_pc_funname (CORE_ADDR pc)
+{
+ symbol *func = find_symbol_for_pc (pc);
+ if (func != nullptr)
+ return find_symbol_funname (func);
+
+ gdb::unique_xmalloc_ptr<char> funname;
+ bound_minimal_symbol msymbol = lookup_minimal_symbol_by_pc (pc);
+ if (msymbol.minsym != nullptr)
+ funname = make_unique_xstrdup (msymbol.minsym->print_name ());
+
+ return funname;
+}
+
+/* Print information of shadow stack frame info FRAME. The output is
+ formatted according to PRINT_WHAT. For the meaning of PRINT_WHAT, see
+ enum print_what comments in frame.h. Note that PRINT_WHAT is overridden,
+ if PRINT_OPTIONS.print_frame_info != print_frame_info_auto. */
+
+static void
+do_print_shadow_stack_frame_info
+ (ui_out *uiout, const frame_print_options &fp_opts,
+ const shadow_stack_frame_info &frame, print_what print_what)
+{
+ /* On x86 there can be a shadow stack token at bit 63. For x32,
+ the address size is only 32 bit. Thus, we still must use
+ gdbarch_shadow_stack_element_size_aligned (and not
+ gdbarch_addr_bit) to determine the width of the address to be
+ printed. */
+ const int element_size
+ = gdbarch_shadow_stack_element_size_aligned (frame.arch);
+
+ if (fp_opts.print_frame_info != print_frame_info_auto)
+ {
+ /* Use the specific frame information desired by the user. */
+ print_what
+ = *print_frame_info_to_print_what (fp_opts.print_frame_info);
+ }
+
+ /* At this point, since FRAME is a shadow stack frame which belongs to a
+ return address, we should always have a valid symtab_and_line object. */
+ gdb_assert (frame.sal.has_value ());
+
+ const symtab_and_line &sal = frame.sal.value ();
+ if (should_print_location (print_what) || sal.symtab == nullptr)
+ {
+ gdb::unique_xmalloc_ptr<char> funname = find_pc_funname (frame.value);
+
+ ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame");
+ uiout->text ("#");
+ uiout->field_fmt_signed (2, ui_left, "level", frame.level);
+
+ uiout->field_string
+ ("addr", hex_string_custom (frame.value, element_size * 2),
+ address_style.style ());
+
+ uiout->text (" in ");
+ print_funname (uiout, funname);
+
+ if (print_what != SHORT_LOCATION && sal.symtab != nullptr)
+ print_filename (uiout, sal);
+
+ if (print_what != SHORT_LOCATION
+ && (funname == nullptr || sal.symtab == nullptr)
+ && sal.pspace != nullptr)
+ {
+ const char *lib = solib_name_from_address (sal.pspace,
+ frame.value);
+ if (lib != nullptr)
+ print_lib (uiout, lib);
+ }
+
+ uiout->text ("\n");
+ }
+
+ if (print_what == SRC_LINE || print_what == SRC_AND_LOC)
+ {
+ bool mid_statement = pc_in_middle_of_statement (frame.value, sal);
+
+ /* While for the ordinary backtrace printing of pc is based on
+ MID_STATEMENT determined by stack.c:frame_show_address and the
+ print configuration, for shadow stack backtrace we always print
+ the pc/address on the shadow stack. */
+ bool print_address = true;
+ print_source (uiout, frame.arch, frame.value, sal, print_address,
+ mid_statement, "");
+ }
+
+ gdb_flush (gdb_stdout);
+}
+
+/* Redirect output to a temporary buffer for the duration of
+ do_print_shadow_stack_frame_info. */
+
+static void
+print_shadow_stack_frame_info
+ (const frame_print_options &fp_opts,
+ const shadow_stack_frame_info &frame,
+ print_what print_what)
+{
+ do_with_buffered_output
+ (do_print_shadow_stack_frame_info, current_uiout,
+ fp_opts, frame, print_what);
+}
+
+/* Extract a char array which can be used for printing a reasonable
+ error message for REASON. REASON must not be NO_ERROR if passed
+ to this function. */
+
+static const char *
+ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
+{
+ switch (reason)
+ {
+ case ssp_unwind_stop_reason::memory_read_error:
+ return _("shadow stack memory read failure");
+ }
+
+ gdb_assert_not_reached ("invalid unwind stop reason.");
+}
+
+/* Read the memory at shadow stack pointer SSP and assign it to
+ RETURN_VALUE. In case of success, return true. Otherwise,
+ return false. */
+
+static bool
+read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
+ CORE_ADDR &return_value)
+{
+ /* On x86 there can be a shadow stack token at bit 63. For x32, the
+ address size is only 32 bit. Thus, we still must use
+ gdbarch_shadow_stack_element_size_aligned (and not gdbarch_addr_bit)
+ to read the full element for x32 as well. */
+ const int element_size
+ = gdbarch_shadow_stack_element_size_aligned (gdbarch);
+
+ const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ return safe_read_memory_unsigned_integer (ssp,
+ element_size,
+ byte_order,
+ &return_value);
+}
+
+/* If possible, return the starting shadow stack frame info needed to handle
+ COUNT outermost frames. This is the equivalent function to
+ stack.c:trailing_outermost_frame which is used to print the normal backtrace.
+ FRAME should point to the innermost (newest) element of the shadow stack.
+ RANGE is the shadow stack memory range [start_address, end_address)
+ corresponding to FRAME's shadow stack pointer. If COUNT is bigger than the
+ number of elements on the shadow stack, return FRAME. In case of failure,
+ assign an appropriate ssp_unwind_stop_reason in FRAME->UNWIND_STOP_REASON. */
+
+static std::optional<shadow_stack_frame_info>
+get_trailing_outermost_shadow_stack_frame_info
+ (const std::pair<CORE_ADDR, CORE_ADDR> range,
+ LONGEST count, shadow_stack_frame_info &frame)
+{
+ gdb_assert (count > 0);
+
+ std::optional<shadow_stack_frame_info> trailing
+ = std::optional<shadow_stack_frame_info> (frame);
+
+ std::optional<shadow_stack_frame_info> current = trailing;
+ while (current.has_value () && count--)
+ {
+ QUIT;
+ current = current->unwind_prev_shadow_stack_frame_info (range);
+ }
+
+ /* Will stop when CURRENT reaches the outermost (oldest) element of the
+ shadow stack. TRAILING will be COUNT below it. */
+ while (current.has_value ())
+ {
+ QUIT;
+ trailing = trailing->unwind_prev_shadow_stack_frame_info (range);
+ /* Since current has a value until this point we can assume that
+ trailing also has a value. */
+ gdb_assert (trailing.has_value ());
+ current = current->unwind_prev_shadow_stack_frame_info (range);
+ }
+
+ return trailing;
+}
+
+/* If possible, get shadow stack frame info for the shadow stack pointer
+ SSP and its current frame LEVEL. Pass FALLBACK_ARCH which can be used
+ as fallback gdbarch in case the gdbarch cannot be extracted from the
+ SAL. Usually this is the gdbarch of the previous frame. */
+
+static std::optional<shadow_stack_frame_info>
+get_shadow_stack_frame_info
+ (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level)
+{
+ CORE_ADDR value;
+ if (!read_shadow_stack_memory (fallback_arch, ssp, value))
+ return {};
+
+ /* At this point, we know that SSP points to VALUE which is a return
+ address. In contrast to find_frame_sal which is used for the normal
+ backtrace command, VALUE always points at the return instruction
+ (which is *after* the call instruction). Since we want to get the
+ line containing the call (because the call is where the user thinks
+ the program is), we pass 1 here as second argument. */
+ symtab_and_line sal = find_sal_for_pc (value, 1);
+ struct gdbarch *sal_arch = get_sal_arch (sal);
+
+ /* Fall back to the previous gdbarch in case we cannot extract it from
+ SAL. */
+ if (sal_arch == nullptr)
+ sal_arch = fallback_arch;
+
+ return std::optional<shadow_stack_frame_info>
+ ({ssp, value, level, sal_arch, sal,
+ ssp_unwind_stop_reason::no_error});
+}
+
+std::optional<shadow_stack_frame_info>
+shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info
+ (std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+ if (!user_set_backtrace_options.backtrace_past_main
+ && this->inside_main_func ())
+ {
+ /* Don't unwind past main(). */
+ return {};
+ }
+
+ /* If the user's backtrace limit has been exceeded, stop. We must
+ add two to the current level; one of those accounts for
+ backtrace_limit being 1-based and the level being 0-based, and the
+ other accounts for the level of the new frame instead of the level
+ of the current frame. */
+ if (this->level + 2 > user_set_backtrace_options.backtrace_limit)
+ return {};
+
+ CORE_ADDR new_ssp
+ = update_shadow_stack_pointer (this->arch, this->ssp,
+ ssp_update_direction::outer);
+
+ if (gdbarch_top_addr_empty_shadow_stack_p (this->arch)
+ && gdbarch_top_addr_empty_shadow_stack (this->arch, new_ssp, range))
+ return {};
+ else if (gdbarch_stack_grows_down (this->arch))
+ {
+ /* The shadow stack grows downwards. */
+ if (new_ssp >= range.second)
+ {
+ /* We reached the outermost element of the shadow stack. */
+ return {};
+ }
+ /* We updated new_ssp towards the outermost element of the shadow
+ stack before, and new_ssp must be pointing to shadow stack
+ memory. */
+ gdb_assert (new_ssp > range.first);
+ }
+ else
+ {
+ /* The shadow stack grows upwards. */
+ if (new_ssp < range.first)
+ {
+ /* We reached the outermost element of the shadow stack. */
+ return {};
+ }
+ /* We updated new_ssp towards the outermost element of the shadow
+ stack before, and new_ssp must be pointing to shadow stack
+ memory. */
+ gdb_assert (new_ssp <= range.second);
+ }
+
+ /* Extract the previous shadow stack frame info (level + 1). */
+ std::optional<shadow_stack_frame_info> new_frame
+ = get_shadow_stack_frame_info (this->arch, new_ssp, this->level + 1);
+
+ if (!new_frame.has_value ())
+ this->unwind_stop_reason = ssp_unwind_stop_reason::memory_read_error;
+
+ return new_frame;
+}
+
+bool
+shadow_stack_frame_info::inside_main_func () const
+{
+ language lang;
+ symbol *sym = find_symbol_for_pc (this->value);
+ if (sym != nullptr)
+ lang = sym->language ();
+ else
+ {
+ bound_minimal_symbol msymbol
+ = lookup_minimal_symbol_by_pc (this->value);
+
+ if (msymbol.minsym != nullptr)
+ lang = msymbol.minsym->language ();
+ else
+ {
+ /* If we cannot extract the symbol, we cannot use the address to
+ find out if the function is inside main and we better print
+ this frame, too. */
+ return false;
+ }
+ }
+
+ std::optional<CORE_ADDR> sym_addr
+ = get_main_func_start_pc (this->arch, lang);
+ if (!sym_addr.has_value () || *sym_addr == 0)
+ return false;
+
+ CORE_ADDR pc_func_start = get_pc_function_start (this->value);
+ if (*sym_addr == pc_func_start)
+ return true;
+
+ return false;
+}
+
+/* Print all elements on the shadow stack or just the innermost COUNT_EXP
+ frames. */
+
+void
+backtrace_shadow_command (const frame_print_options &fp_opts,
+ const char *count_exp, int from_tty)
+{
+ if (!target_has_stack ())
+ error (_("No shadow stack."));
+
+ gdbarch *gdbarch = get_current_arch ();
+ if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
+ || !gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
+ || gdbarch_ssp_regnum (gdbarch) == -1)
+ error (_("Printing of the shadow stack backtrace is not supported for"
+ " the current target."));
+
+ regcache *regcache = get_thread_regcache (inferior_thread ());
+ bool shadow_stack_enabled = false;
+
+ std::optional<CORE_ADDR> start_ssp
+ = gdbarch_get_shadow_stack_pointer (gdbarch, regcache,
+ shadow_stack_enabled);
+
+ if (!start_ssp.has_value () || !shadow_stack_enabled)
+ error (_("Shadow stack is not enabled for the current thread."));
+
+ /* Check if START_SSP points to a shadow stack memory range and use
+ the returned range to determine when to stop unwinding.
+ 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
+ command without support for shadow stack switches for now. */
+ bool is_top_addr_empty_shadow_stack = false;
+ std::pair<CORE_ADDR, CORE_ADDR> range;
+ if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
+ &range))
+ {
+ /* For x86, if the current shadow stack pointer does not point to
+ shadow stack memory but is valid, the shadow stack is empty. */
+ is_top_addr_empty_shadow_stack = true;
+ }
+
+ if (!is_top_addr_empty_shadow_stack)
+ {
+ /* For ARM's Guarded Control Stack, the shadow stack can be empty
+ even though START_SSP points to shadow stack memory range. */
+ is_top_addr_empty_shadow_stack
+ = gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
+ && gdbarch_top_addr_empty_shadow_stack (gdbarch, *start_ssp, range);
+ }
+
+ if (is_top_addr_empty_shadow_stack)
+ {
+ gdb_printf (_("The shadow stack is empty.\n"));
+ return;
+ }
+
+ /* Extract the first shadow stack frame info (level 0). */
+ ssp_unwind_stop_reason reason = ssp_unwind_stop_reason::no_error;
+ std::optional<shadow_stack_frame_info> current
+ = get_shadow_stack_frame_info (gdbarch, *start_ssp, 0);
+
+ if (!current.has_value ())
+ reason = ssp_unwind_stop_reason::memory_read_error;
+
+ std::optional<shadow_stack_frame_info> trailing = current;
+
+ LONGEST count = -1;
+ if (current.has_value () && count_exp != nullptr)
+ {
+ count = parse_and_eval_long (count_exp);
+ /* If count is negative, update trailing with the shadow stack frame
+ info from which we should start printing. */
+ if (count < 0)
+ {
+ trailing = get_trailing_outermost_shadow_stack_frame_info
+ (range, std::abs (count), *current);
+
+ if (!trailing.has_value ())
+ reason = current->unwind_stop_reason;
+ }
+ }
+
+ if (!trailing.has_value ())
+ {
+ gdb_assert (reason != ssp_unwind_stop_reason::no_error);
+
+ error (_("Cannot print shadow stack backtrace: %s.\n"),
+ ssp_unwind_stop_reason_to_err_string (reason));
+ }
+
+ for (current = trailing; current.has_value () && count != 0; count--)
+ {
+ QUIT;
+
+ print_shadow_stack_frame_info (fp_opts, *current, LOCATION);
+
+ trailing = current;
+ current = current->unwind_prev_shadow_stack_frame_info (range);
+ }
+
+ /* If we've stopped before the end, mention that. */
+ if (current.has_value () && from_tty)
+ gdb_printf (_("(More shadow stack frames follow...)\n"));
+
+ /* Due to the loop above, trailing always has a value at this point. */
+ gdb_assert (trailing.has_value ());
+
+ /* If we've run out of shadow stack frames, and the reason appears to
+ be an error condition, print it. */
+ if (!current.has_value ()
+ && trailing->unwind_stop_reason > ssp_unwind_stop_reason::no_error)
+ gdb_printf (_("Shadow stack backtrace stopped at shadow stack " \
+ "pointer %s due to: %s.\n"),
+ paddress (trailing->arch, trailing->ssp),
+ ssp_unwind_stop_reason_to_err_string
+ (trailing->unwind_stop_reason));
+}
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 5f540ff7d62..5c89aee70bb 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -21,9 +21,17 @@
#ifndef GDB_SHADOW_STACK_H
#define GDB_SHADOW_STACK_H
+#include <optional>
+#include <utility>
+#include <string>
+
+#include "symtab.h"
+
class regcache;
class frame_info_ptr;
struct value;
+struct frame_print_options;
+struct gdbarch;
/* If shadow stack is enabled, push the address NEW_ADDR on the shadow
stack and update the shadow stack pointer accordingly. */
@@ -39,4 +47,61 @@ void shadow_stack_push (regcache *regcache, const CORE_ADDR new_addr);
value *dwarf2_prev_ssp (const frame_info_ptr &this_frame,
void **this_cache, int regnum);
+/* Implementation of "backtrace -shadow" command line option. */
+
+void backtrace_shadow_command
+ (const frame_print_options &fp_opts,
+ const char *count_exp, int from_tty);
+
+enum class ssp_unwind_stop_reason
+{
+ /* No particular reason; either we haven't tried unwinding yet, or we
+ didn't fail. */
+ no_error = 0,
+
+ /* We could not read the memory of the shadow stack element. */
+ memory_read_error,
+};
+
+/* Information of a shadow stack frame belonging to a shadow stack element
+ at shadow stack pointer SSP. */
+
+class shadow_stack_frame_info
+{
+public:
+ /* If possible, unwind the previous shadow stack frame info. RANGE is
+ the shadow stack memory range [start_address, end_address) belonging
+ to this frame's shadow stack pointer. If we cannot unwind the
+ previous frame info, set the unwind_stop_reason attribute. If we
+ reached the bottom of the shadow stack just don't return a value. */
+ std::optional<shadow_stack_frame_info> unwind_prev_shadow_stack_frame_info
+ (std::pair<CORE_ADDR, CORE_ADDR> range);
+
+ /* The shadow stack pointer. */
+ CORE_ADDR ssp;
+
+ /* The value of the shadow stack at SSP. */
+ CORE_ADDR value;
+
+ /* The level of the element on the shadow stack. */
+ unsigned long level;
+
+ /* Architecture of the current shadow stack frame. */
+ struct gdbarch *arch;
+
+ /* Optional SAL object of the current shadow stack frame. */
+ std::optional<symtab_and_line> sal;
+
+ /* If unwinding of the previous frame info fails assign this value to a
+ matching condition ssp_unwind_stop_reason
+ > ssp_unwind_stop_reason::no_error. */
+ ssp_unwind_stop_reason unwind_stop_reason
+ = ssp_unwind_stop_reason::no_error;
+
+ /* Return true if this frame is inside the "main"() function. If the frame
+ is outside the "main"() function, does not have a SAL object or if the
+ symbol information cannot be extracted, return false. */
+ bool inside_main_func () const;
+};
+
#endif /* GDB_SHADOW_STACK_H */
diff --git a/gdb/stack.c b/gdb/stack.c
index 7df1d6e33da..e96fbe73947 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -51,6 +51,7 @@
#include "cli/cli-utils.h"
#include "objfiles.h"
#include "buffered-streams.h"
+#include "shadow-stack.h"
#include "symfile.h"
#include "extension.h"
@@ -191,6 +192,7 @@ struct backtrace_cmd_options
bool full = false;
bool no_filters = false;
bool hide = false;
+ bool shadow = false;
};
using bt_flag_option_def
@@ -214,6 +216,14 @@ static const gdb::option::option_def backtrace_command_option_defs[] = {
[] (backtrace_cmd_options *opt) { return &opt->hide; },
N_("Causes Python frame filter elided frames to not be printed."),
},
+
+ bt_flag_option_def {
+ "shadow",
+ [] (backtrace_cmd_options *opt) { return &opt->shadow; },
+ N_("Print shadow stack frames instead of normal frames.\n\
+This option may be combined with \"-frame-info\" and\n\
+implies '-no-filters' and '-frame-arguments none'."),
+ },
};
/* Prototypes for local functions. */
@@ -906,11 +916,9 @@ do_gdb_disassembly (struct gdbarch *gdbarch,
}
}
-/* Converts the PRINT_FRAME_INFO choice to an optional enum print_what.
- Value not present indicates to the caller to use default values
- specific to the command being executed. */
+/* See stack.h. */
-static std::optional<enum print_what>
+std::optional<enum print_what>
print_frame_info_to_print_what (const char *print_frame_info)
{
for (int i = 0; print_frame_info_choices[i] != NULL; i++)
@@ -960,7 +968,7 @@ get_user_print_what_frame_info (std::optional<enum print_what> *what)
/* Return true if PRINT_WHAT is configured to print the location of a
frame. */
-static bool
+bool
should_print_location (print_what print_what)
{
return (print_what == LOCATION
@@ -969,14 +977,9 @@ should_print_location (print_what print_what)
|| print_what == SHORT_LOCATION);
}
-/* Print the source information for PC and SAL to UIOUT. Based on the
- user-defined configuration disassemble-next-line, display disassembly
- of the next source line, in addition to displaying the source line
- itself. Print annotations describing source file and and line number
- based on MID_STATEMENT information. If SHOW_ADDRESS is true, print the
- program counter PC including, if non-empty, PC_ADDRESS_FLAGS. */
+/* See stack.h. */
-static void
+void
print_source (ui_out *uiout, gdbarch *gdbarch, CORE_ADDR pc,
symtab_and_line sal, bool show_address, int mid_statement,
const std::string &pc_address_flags)
@@ -1242,7 +1245,7 @@ get_last_displayed_sal ()
/* Find the function name for the symbol SYM. */
-static gdb::unique_xmalloc_ptr<char>
+gdb::unique_xmalloc_ptr<char>
find_symbol_funname (const symbol *sym)
{
gdb::unique_xmalloc_ptr<char> funname;
@@ -1308,10 +1311,9 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang,
return funname;
}
-/* Print the library LIB to UIOUT for the printing of frame
- information. */
+/* See stack.h. */
-static void
+void
print_lib (ui_out *uiout, const char *lib)
{
annotate_frame_where ();
@@ -1320,10 +1322,7 @@ print_lib (ui_out *uiout, const char *lib)
uiout->field_string ("from", lib, file_name_style.style ());
}
-/* Print the filenname of SAL to UIOUT for the printing of frame
- information. */
-
-static void
+void
print_filename (ui_out *uiout, symtab_and_line sal)
{
annotate_frame_source_begin ();
@@ -1348,10 +1347,9 @@ print_filename (ui_out *uiout, symtab_and_line sal)
annotate_frame_source_end ();
}
-/* If available, print FUNNAME to UIOUT for the printing of frame
- information. */
+/* See stack.h. */
-static void
+void
print_funname (ui_out *uiout,
gdb::unique_xmalloc_ptr<char> const &funname)
{
@@ -2162,7 +2160,10 @@ backtrace_command (const char *arg, int from_tty)
scoped_restore restore_set_backtrace_options
= make_scoped_restore (&user_set_backtrace_options, set_bt_opts);
- backtrace_command_1 (fp_opts, bt_cmd_opts, arg, from_tty);
+ if (!bt_cmd_opts.shadow)
+ backtrace_command_1 (fp_opts, bt_cmd_opts, arg, from_tty);
+ else
+ backtrace_shadow_command (fp_opts, arg, from_tty);
}
/* Completer for the "backtrace" command. */
diff --git a/gdb/stack.h b/gdb/stack.h
index ad2700b59a7..3f4cf348841 100644
--- a/gdb/stack.h
+++ b/gdb/stack.h
@@ -84,4 +84,47 @@ void frame_apply_all_cmd_completer (struct cmd_list_element *ignore,
completion_tracker &tracker,
const char *text, const char */*word*/);
+/* Print the filename of SAL to UIOUT for the printing of frame
+ information. */
+
+void print_filename (ui_out *uiout, symtab_and_line sal);
+
+/* Print the library LIB to UIOUT for the printing of frame
+ information. */
+
+void print_lib (ui_out *uiout, const char *lib);
+
+/* If available, print FUNNAME to UIOUT for the printing of frame
+ information. */
+
+void print_funname (ui_out *uiout,
+ gdb::unique_xmalloc_ptr<char> const &funname);
+
+/* Converts the PRINT_FRAME_INFO choice to an optional enum print_what.
+ Value not present indicates to the caller to use default values
+ specific to the command being executed. */
+
+std::optional<print_what> print_frame_info_to_print_what
+ (const char *print_frame_info);
+
+/* Return true if PRINT_WHAT is configured to print the location of a
+ frame. */
+
+bool should_print_location (print_what print_what);
+
+/* Print the source information for PC and SAL to UIOUT. Based on the
+ user-defined configuration disassemble-next-line, display disassembly
+ of the next source line, in addition to displaying the source line
+ itself. Print annotations describing source file and and line number
+ based on MID_STATEMENT information. If SHOW_ADDRESS is true, print the
+ program counter PC including, if non-empty, PC_ADDRESS_FLAGS. */
+
+void print_source (ui_out *uiout, gdbarch *gdbarch, CORE_ADDR pc,
+ symtab_and_line sal, bool show_address,
+ int mid_statement, const std::string &pc_address_flags);
+
+/* Find the function name for the symbol SYM. */
+
+gdb::unique_xmalloc_ptr<char> find_symbol_funname (const symbol *sym);
+
#endif /* GDB_STACK_H */
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
index e67a0c2e4a1..0347313b647 100644
--- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
@@ -109,6 +109,53 @@ save_vars { ::env(GLIBC_TUNABLES) } {
gdb_test "print /x \$pl3_ssp" "= $ssp_call2" "check pl3_ssp of frame 0"
}
+ set fill "\[^\r\n\]+"
+ # Build shadow stack frames to test the 'backtrace -shadow' command.
+ set sspval_main [get_valueof "/z" "*(long long int*)$ssp_main" ""]
+ set sspval_call1 [get_valueof "/z" "*(long long int*)$ssp_call1" ""]
+ set sspval_call2 [get_valueof "/z" "*(long long int*)$ssp_call2" ""]
+ set frame1 "#0\[ \t\]*$sspval_call2 in call1$fill"
+ set frame2 "#1\[ \t\]*$sspval_call1 in main$fill"
+ set frame3 "#2\[ \t\]*$sspval_main$fill"
+
+ # Test bt -shadow, which prints until the main function.
+ gdb_test "bt -shadow" \
+ [multi_line \
+ "$frame1" \
+ "$frame2" ] \
+ "test shadow stack backtrace until main."
+
+ # With -past-main set, we can still only test that we print the first
+ # 3 frames correctly, as the shadow stack enablement might depend on
+ # the underlying OS.
+ gdb_test "bt -shadow -past-main" \
+ [multi_line \
+ "$frame1" \
+ "$frame2" \
+ "$frame3" \
+ ".*" ] \
+ "test shadow stack backtrace until the bottom of the stack."
+
+ gdb_test "bt -shadow 1" \
+ [multi_line \
+ "$frame1" \
+ "\\(More shadow stack frames follow...\\)" ] \
+ "test shadow stack backtrace with a positive value for count"
+
+ # We can only test that we print a single frame, as the shadow stack
+ # enablement might depend on the underlying OS.
+ gdb_test "bt -shadow -1" "^#$decimal\[ \t\]*$hex$fill" \
+ "test shadow stack backtrace with a negative value for count"
+
+ # Test backtrace limit
+ gdb_test_no_output "set backtrace limit 2"
+ gdb_test "bt -shadow" \
+ [multi_line \
+ "$frame1" \
+ "$frame2" ] \
+ "test shadow stack backtrace with limit"
+ gdb_test_no_output "set backtrace limit unlimited" "restore backtrace limit default"
+
with_test_prefix "test return from current frame" {
gdb_test "return (int) 1" "#0.*call1.*" \
"Test shadow stack return from current frame" \
diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp
index 35487ead6a7..8ca6e677aba 100644
--- a/gdb/testsuite/gdb.base/options.exp
+++ b/gdb/testsuite/gdb.base/options.exp
@@ -286,7 +286,6 @@ proc_with_prefix test-backtrace {} {
clean_restart
test_gdb_complete_unique "backtrace" "backtrace"
- test_gdb_complete_none "backtrace "
gdb_test "backtrace -" "Ambiguous option at: -"
gdb_test "backtrace --" "No stack\\."
@@ -302,6 +301,7 @@ proc_with_prefix test-backtrace {} {
"-past-entry"
"-past-main"
"-raw-frame-arguments"
+ "-shadow"
}
# Test that we complete the qualifiers, if there's any.
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 09/13] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (7 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 08/13] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 10/13] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
On x86 with CET or on ARM with GCS, there can be elements on the shadow
stack which are not return addresses. In this case, we don't want to print
the shadow stack element, but a string instead which describes the frame
similar to the normal backtrace command for dummy frames or signals.
Provide a gdbarch hook to distinguish between return and non-return
addresses and to configure a string which is printed instead of the
shadow stack element.
---
gdb/doc/gdb.texinfo | 19 +++++++++++++++++++
gdb/gdbarch-gen.c | 32 ++++++++++++++++++++++++++++++++
gdb/gdbarch-gen.h | 19 +++++++++++++++++++
gdb/gdbarch_components.py | 25 +++++++++++++++++++++++++
gdb/shadow-stack.c | 36 +++++++++++++++++++++++++++++++++++-
gdb/shadow-stack.h | 26 ++++++++++++++++++++++++++
6 files changed, 156 insertions(+), 1 deletion(-)
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 29ad2d24ca6..665576c4fc4 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8811,6 +8811,25 @@ This is how a shadow stack backtrace looks like on amd64:
@end group
@end smallexample
+There can be elements on the shadow stack which are not return addresses,
+for example on x86 with the Intel Control-Flow Enforcement Technology
+(@xref{CET}). In case of signals, the old shadow stack pointer is pushed
+in a special format with bit 63 set. See @url{https://docs.kernel.org/arch/x86/shstk.html}
+for more details. For such shadow stack elements, the shadow stack
+frame just contains the level and a string describing the shadow stack
+element:
+
+@smallexample
+@group
+(gdb) bt -shadow 4
+#0 0x00007ffff7c45330 in __restore_rt from /lib/x86_64-linux-gnu/libc.so.6
+#1 <sigframe token>
+#2 0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:26
+#3 0x000055555555519d in main at tmp/amd64-shadow-stack-signal.c:29
+(More shadow stack frames follow...)
+@end group
+@end smallexample
+
@end table
The optional @var{qualifier} is maintained for backward compatibility.
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index 022ad496026..e93afff34b1 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -256,6 +256,7 @@ struct gdbarch
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;
+ gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address = nullptr;
};
/* Create a new ``struct gdbarch'' based on information provided by
@@ -519,6 +520,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
/* 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. */
+ /* Skip verify of is_no_return_shadow_stack_address, has predicate. */
if (!log.empty ())
internal_error (_("verify_gdbarch: the following are invalid ...%s"),
log.c_str ());
@@ -1357,6 +1359,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
gdb_printf (file,
"gdbarch_dump: shadow_stack_element_size_aligned = %s\n",
plongest (gdbarch->shadow_stack_element_size_aligned));
+ gdb_printf (file,
+ "gdbarch_dump: gdbarch_is_no_return_shadow_stack_address_p() = %d\n",
+ gdbarch_is_no_return_shadow_stack_address_p (gdbarch));
+ gdb_printf (file,
+ "gdbarch_dump: is_no_return_shadow_stack_address = <%s>\n",
+ host_address_to_string (gdbarch->is_no_return_shadow_stack_address));
if (gdbarch->dump_tdep != nullptr)
gdbarch->dump_tdep (gdbarch, file);
}
@@ -5362,3 +5370,27 @@ set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch,
{
gdbarch->shadow_stack_element_size_aligned = shadow_stack_element_size_aligned;
}
+
+bool
+gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch)
+{
+ gdb_assert (gdbarch != nullptr);
+ return gdbarch->is_no_return_shadow_stack_address != nullptr;
+}
+
+std::optional<shadow_stack_frame_info>
+gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level)
+{
+ gdb_assert (gdbarch != nullptr);
+ gdb_assert (gdbarch->is_no_return_shadow_stack_address != nullptr);
+ if (gdbarch_debug >= 2)
+ gdb_printf (gdb_stdlog, "gdbarch_is_no_return_shadow_stack_address called\n");
+ return gdbarch->is_no_return_shadow_stack_address (gdbarch, ssp, value, level);
+}
+
+void
+set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch,
+ gdbarch_is_no_return_shadow_stack_address_ftype is_no_return_shadow_stack_address)
+{
+ gdbarch->is_no_return_shadow_stack_address = is_no_return_shadow_stack_address;
+}
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index e4f9bc21b1d..791324777dd 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1789,3 +1789,22 @@ void set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, gdbarch_t
int gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch);
void set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch, int shadow_stack_element_size_aligned);
+
+/* There can be elements on the shadow stack which are not return addresses.
+ This happens for example on x86 with CET in case of signals.
+ If an architecture implements the command option 'backtrace -shadow' and
+ the shadow stack can contain elements which are not return addresses, this
+ function has to be provided.
+ Return a shadow stack frame info with frame type
+ ssp_frame_type::non_return_frame, if the shadow stack pointer SSP belongs
+ to a valid shadow stack frame while the element on the shadow stack
+ VALUE does not refer to a return address. In that case, also the frame's
+ attribute non_return_description has to be set to a string which is
+ displayed instead of the element on the shadow stack in the shadow stack
+ backtrace. Otherwise, return an empty optional. */
+
+bool gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch);
+
+using gdbarch_is_no_return_shadow_stack_address_ftype = std::optional<shadow_stack_frame_info> (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level);
+std::optional<shadow_stack_frame_info> gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level);
+void set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address);
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index 9d6fdc5f941..0435b1b5eca 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2839,3 +2839,28 @@ this value.
# linux starting with Linux kernel v6.6.
unused=True,
)
+
+Method(
+ comment="""
+There can be elements on the shadow stack which are not return addresses.
+This happens for example on x86 with CET in case of signals.
+If an architecture implements the command option 'backtrace -shadow' and
+the shadow stack can contain elements which are not return addresses, this
+function has to be provided.
+Return a shadow stack frame info with frame type
+ssp_frame_type::non_return_frame, if the shadow stack pointer SSP belongs
+to a valid shadow stack frame while the element on the shadow stack
+VALUE does not refer to a return address. In that case, also the frame's
+attribute non_return_description has to be set to a string which is
+displayed instead of the element on the shadow stack in the shadow stack
+backtrace. Otherwise, return an empty optional.
+""",
+ type="std::optional<shadow_stack_frame_info>",
+ name="is_no_return_shadow_stack_address",
+ params=[
+ ("const CORE_ADDR", "ssp"),
+ ("const CORE_ADDR", "value"),
+ ("const unsigned long", "level"),
+ ],
+ predicate=True,
+)
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 49b831617ab..dbd8959753d 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -233,6 +233,29 @@ do_print_shadow_stack_frame_info
const int element_size
= gdbarch_shadow_stack_element_size_aligned (frame.arch);
+ if (frame.type != ssp_frame_type::normal_frame)
+ {
+ std::string str;
+ if (frame.type == ssp_frame_type::non_return_frame)
+ {
+ /* For non-return frames, the string must have a value. */
+ gdb_assert (frame.non_return_description.has_value ());
+ str = frame.non_return_description.value ();
+ }
+ else
+ gdb_assert_not_reached ("Invalid shadow stack frame type.");
+
+ ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame");
+ uiout->text ("#");
+ uiout->field_fmt_signed (2, ui_left, "level", frame.level);
+
+ uiout->field_string ("func", str, metadata_style.style ());
+
+ uiout->text ("\n");
+ gdb_flush (gdb_stdout);
+ return;
+ }
+
if (fp_opts.print_frame_info != print_frame_info_auto)
{
/* Use the specific frame information desired by the user. */
@@ -398,6 +421,15 @@ get_shadow_stack_frame_info
if (!read_shadow_stack_memory (fallback_arch, ssp, value))
return {};
+ if (gdbarch_is_no_return_shadow_stack_address_p (fallback_arch))
+ {
+ std::optional<shadow_stack_frame_info> no_return_frame
+ = gdbarch_is_no_return_shadow_stack_address (fallback_arch, ssp,
+ value, level);
+ if (no_return_frame.has_value ())
+ return *no_return_frame;
+ }
+
/* At this point, we know that SSP points to VALUE which is a return
address. In contrast to find_frame_sal which is used for the normal
backtrace command, VALUE always points at the return instruction
@@ -414,6 +446,7 @@ get_shadow_stack_frame_info
return std::optional<shadow_stack_frame_info>
({ssp, value, level, sal_arch, sal,
+ ssp_frame_type::normal_frame, {},
ssp_unwind_stop_reason::no_error});
}
@@ -421,7 +454,8 @@ std::optional<shadow_stack_frame_info>
shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info
(std::pair<CORE_ADDR, CORE_ADDR> range)
{
- if (!user_set_backtrace_options.backtrace_past_main
+ if (this->type == ssp_frame_type::normal_frame
+ && !user_set_backtrace_options.backtrace_past_main
&& this->inside_main_func ())
{
/* Don't unwind past main(). */
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 5c89aee70bb..4278620c70c 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -63,6 +63,21 @@ enum class ssp_unwind_stop_reason
memory_read_error,
};
+enum class ssp_frame_type
+{
+ /* A normal shadow stack frame which belongs to a return address of the
+ program execution flow. */
+ normal_frame,
+
+ /* A shadow stack frame which does not belong to a return address.
+ It is possible, on x86 for instance, that an element on the shadow
+ stack is not a return address. We don't want to print the address
+ in that case but only a string describing that specific frame type.
+ This frame type is configured in the target specific implementation
+ of is_no_return_shadow_stack_address. */
+ non_return_frame,
+};
+
/* Information of a shadow stack frame belonging to a shadow stack element
at shadow stack pointer SSP. */
@@ -92,6 +107,17 @@ class shadow_stack_frame_info
/* Optional SAL object of the current shadow stack frame. */
std::optional<symtab_and_line> sal;
+ /* The shadow stack frame type. */
+ ssp_frame_type type;
+
+ /* If this is not a normal shadow stack frame belonging to a return
+ address of the program execution flow then the frame will have the
+ type ssp_frame_type::non_return_frame. In this case, this string
+ is configured to describe this specific frame type and it will
+ be printed in the shadow stack backtrace instead of the element on
+ the shadow stack. */
+ std::optional<std::string> non_return_description;
+
/* If unwinding of the previous frame info fails assign this value to a
matching condition ssp_unwind_stop_reason
> ssp_unwind_stop_reason::no_error. */
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 10/13] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (8 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 09/13] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 11/13] gdb: Enable inferior calls in the shadow stack backtrace Christina Schimpe
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
There can be elements on the shadow stack which are not return addresses.
This can happen, for instance, in case of signals on amd64 linux.
The old shadow stack pointer is pushed in a special format with bit 63 set.
|1...old SSP| - Pointer to old pre-signal ssp in sigframe token format
(bit 63 set to 1)
Linux kernel documentation: https://docs.kernel.org/arch/x86/shstk.html
Implement the gdbarch hook is_no_return_shadow_stack_address to detect
this scenario to print the shadow stack backtrace correctly.
---
gdb/amd64-linux-tdep.c | 59 +++++++++++++++++++
.../amd64-shadow-stack-backtrace-signal.exp | 49 +++++++++++++++
.../gdb.arch/amd64-shadow-stack-signal.c | 31 ++++++++++
3 files changed, 139 insertions(+)
create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index 42a62eaa975..d98415b8989 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -1960,6 +1960,62 @@ amd64_linux_top_addr_empty_shadow_stack
return addr == range.second;
}
+/* Return a shadow stack frame info, if the shadow stack pointer SSP
+ belongs to a valid shadow stack frame while the element on the shadow
+ stack VALUE does not refer to a return address. This can happen, for
+ instance, in case of signals. The old shadow stack pointer is pushed
+ in a special format with bit 63 set. In case this is true, a valid
+ shadow stack frame info is returned with its attributes frame_type and
+ non_return_description configured to ssp_frame_type::non_return_frame
+ and "<sigframe token>", respectively. */
+
+static std::optional<shadow_stack_frame_info>
+amd64_linux_is_no_return_shadow_stack_address
+ (gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value,
+ const unsigned long level)
+{
+ /* SSP must belong to the shadow stack memory range. */
+ std::pair<CORE_ADDR, CORE_ADDR> range;
+ gdb_assert (gdbarch_address_in_shadow_stack_memory_range (gdbarch,
+ ssp,
+ &range));
+
+ /* In case bit 63 is not configured, the address on the shadow stack
+ should be a return address. */
+ constexpr CORE_ADDR mask = (CORE_ADDR) 1 << 63;
+ if ((value & mask) == 0)
+ return {};
+
+ /* To compare the shadow stack pointer of the previous frame with the
+ value of FRAME, we must clear bit 63. */
+ CORE_ADDR shadow_stack_val_cleared = (value & (~mask));
+
+ /* Compute the previous/old SSP. The shadow stack grows downwards. To
+ compute the previous shadow stack pointer, we need to increment
+ SSP. */
+ CORE_ADDR prev_ssp
+ = ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch);
+
+ /* We incremented SSP by one element to compute PREV_SSP before. In
+ case SSP points to the first element of the shadow stack, PREV_SSP
+ must point to the bottom of the shadow stack (RANGE.SECOND), but not
+ beyond that address. */
+ gdb_assert (prev_ssp > range.first && prev_ssp <= range.second);
+
+ if (shadow_stack_val_cleared == prev_ssp)
+ {
+ /* Assign the current gdbarch to the new shadow stack frame. Since
+ the token is no PC value, do not assign a SAL object. */
+ return std::optional<shadow_stack_frame_info>
+ ({ssp, value, level, gdbarch, {},
+ ssp_frame_type::non_return_frame,
+ {"<sigframe token>"},
+ ssp_unwind_stop_reason::no_error});
+ }
+
+ return {};
+}
+
static void
amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
int num_disp_step_buffers)
@@ -2023,6 +2079,9 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
set_gdbarch_top_addr_empty_shadow_stack
(gdbarch, amd64_linux_top_addr_empty_shadow_stack);
+
+ set_gdbarch_is_no_return_shadow_stack_address
+ (gdbarch, amd64_linux_is_no_return_shadow_stack_address);
}
static void
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
new file mode 100644
index 00000000000..21373dc07f3
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
@@ -0,0 +1,49 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test shadow stack backtrace for signal handling on linux.
+
+require allow_ssp_tests {istarget "*-*-linux*"}
+
+standard_testfile amd64-shadow-stack-signal.c
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+
+ append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+ if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+ {debug additional_flags="-fcf-protection=return"}] } {
+ return
+ }
+
+ if { ![runto_main] } {
+ return
+ }
+
+ gdb_breakpoint "handler"
+ gdb_test "continue" \
+ ".*Program received signal SIGUSR1, User defined signal 1.*" \
+ "continue until signal"
+ gdb_continue_to_breakpoint "continue to breakpoint in handler"
+
+ # Test shadow stack backtrace including <sigframe token>.
+ gdb_test "bt -shadow" \
+ [multi_line \
+ "#0\[ \t\]*$hex in \[^\r\n\]+" \
+ "#1\[ \t\]*<sigframe token>" \
+ "#2\[ \t\]*$hex in \[^\r\n\]+" \
+ ".*" ] \
+ "test shadow stack backtrace for signal handling."
+}
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c b/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c
new file mode 100644
index 00000000000..c726e05b224
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c
@@ -0,0 +1,31 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2024-2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <signal.h>
+
+void
+handler (int signo)
+{
+}
+
+int
+main (void)
+{
+ signal (SIGUSR1, handler);
+ raise (SIGUSR1);
+ return 0;
+}
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 11/13] gdb: Enable inferior calls in the shadow stack backtrace.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (9 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 10/13] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 12/13] gdb: Enable signal trampolines " Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 13/13] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
Similar to the normal backtrace display <function called from gdb>
in the shadow stack backtrace for inferior function calls.
Example for a nested inferior call:
~~~
(gdb) bt -shadow
\#0 <function called from gdb>
\#1 <function called from gdb>
\#2 0x000000000040045a in call1 at /tmp/amd64-shadow-stack.c:27
\#3 0x0000000000400466 in main at /tmp/amd64-shadow-stack.c:38
(gdb) bt
\#0 call2 () at /tmp/amd64-shadow-stack.c:21
\#1 <function called from gdb>
\#2 call2 () at /tmp/amd64-shadow-stack.c:21
\#3 <function called from gdb>
\#4 call2 () at /tmp/amd64-shadow-stack.c:21
\#5 0x000000000040045a in call1 () at /tmp/amd64-shadow-stack.c:27
\#6 0x0000000000400466 in main () at /tmp/amd64-shadow-stack.c:38
~~~
---
gdb/shadow-stack.c | 36 +++++++++++++++++++
gdb/shadow-stack.h | 4 +++
.../gdb.arch/amd64-shadow-stack-cmds.exp | 24 +++++++++++++
3 files changed, 64 insertions(+)
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index dbd8959753d..94062034bd3 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -215,6 +215,29 @@ find_pc_funname (CORE_ADDR pc)
return funname;
}
+/* Check if the shadow stack frame's value VAL is an inferior call
+ return address. */
+
+static bool
+is_infcall_return_address (const CORE_ADDR val)
+{
+ const int inf_thread_num = inferior_thread ()->global_num;
+
+ for (breakpoint &b : all_breakpoints ())
+ {
+ if (b.type != bp_call_dummy
+ || b.thread != inf_thread_num
+ || b.frame_id.code_addr != val)
+ continue;
+
+ for (bp_location &bl : b.locations ())
+ if (bl.pspace == current_program_space)
+ return true;
+ }
+
+ return false;
+}
+
/* Print information of shadow stack frame info FRAME. The output is
formatted according to PRINT_WHAT. For the meaning of PRINT_WHAT, see
enum print_what comments in frame.h. Note that PRINT_WHAT is overridden,
@@ -242,6 +265,8 @@ do_print_shadow_stack_frame_info
gdb_assert (frame.non_return_description.has_value ());
str = frame.non_return_description.value ();
}
+ else if (frame.type == ssp_frame_type::dummy_frame)
+ str = "<function called from gdb>";
else
gdb_assert_not_reached ("Invalid shadow stack frame type.");
@@ -430,6 +455,17 @@ get_shadow_stack_frame_info
return *no_return_frame;
}
+ if (is_infcall_return_address (value))
+ {
+ /* If VALUE belongs to a dummy call breakpoint get_sal_arch won't
+ return a valid gdbarch and we can assign the current gdbarch
+ here. We also don't show any SAL information, so we can set it
+ to an empty optional. */
+ return std::optional<shadow_stack_frame_info>
+ ({ssp, value, level, fallback_arch, {}, ssp_frame_type::dummy_frame,
+ {}, ssp_unwind_stop_reason::no_error});
+ }
+
/* At this point, we know that SSP points to VALUE which is a return
address. In contrast to find_frame_sal which is used for the normal
backtrace command, VALUE always points at the return instruction
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 4278620c70c..225fd3996cd 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -76,6 +76,10 @@ enum class ssp_frame_type
This frame type is configured in the target specific implementation
of is_no_return_shadow_stack_address. */
non_return_frame,
+
+ /* A fake frame, created by GDB when performing an inferior function
+ call. */
+ dummy_frame,
};
/* Information of a shadow stack frame belonging to a shadow stack element
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
index 0347313b647..3259fd2f918 100644
--- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
@@ -167,6 +167,30 @@ save_vars { ::env(GLIBC_TUNABLES) } {
gdb_continue_to_end
}
+ with_test_prefix "test inferior call shadow stack backtrace" {
+ restart_and_run_infcall_call2
+ gdb_test "bt -shadow" \
+ [multi_line \
+ "#0\[ \t\]*<function called from gdb>" \
+ "#1\[ \t\]*$hex in \[^\r\n\]+" \
+ "#2\[ \t\]*$hex in \[^\r\n\]+" ] \
+ "Test shadow stack backtrace for inferior calls."
+
+ set inside_infcall_str \
+ "The program being debugged stopped while in a function called from GDB"
+ gdb_test "call (int) call2()" \
+ "Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" \
+ "Execute an inferior call inside an inferior call."
+
+ gdb_test "bt -shadow" \
+ [multi_line \
+ "#0\[ \t\]*<function called from gdb>" \
+ "#1\[ \t\]*<function called from gdb>" \
+ "#2\[ \t\]*$hex in \[^\r\n\]+" \
+ "#3\[ \t\]*$hex in \[^\r\n\]+" ] \
+ "Test shadow stack backtrace for nested inferior calls."
+ }
+
clean_restart ${::testfile}
if { ![runto_main] } {
return
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 12/13] gdb: Enable signal trampolines in the shadow stack backtrace.
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (10 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 11/13] gdb: Enable inferior calls in the shadow stack backtrace Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 13/13] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
Similar to the normal backtrace we now print "<signal handler called>"
in the shadow stack backtrace, too.
~~~
(gdb) bt -shadow
/#0 <signal handler called>
/#1 <sigframe token>
/#2 0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:26
/#3 0x0000555555555175 in main at /tmp/gdb.arch/amd64-shadow-stack-signal.c:29
(gdb) bt
/#0 handler (signo=10) at /tmp/amd64-shadow-stack-signal.c:23
/#1 <signal handler called>
/#2 __pthread_kill_implementation (no_tid=0, signo=10, threadid=<optimized out>) at ./nptl/pthread_kill.c:44
/#3 __pthread_kill_internal (signo=10, threadid=<optimized out>) at ./nptl/pthread_kill.c:78
/#4 __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=10) at ./nptl/pthread_kill.c:89
/#5 0x00007ffff7c4527e in __GI_raise (sig=10) at ../sysdeps/posix/raise.c:26
/#6 0x0000555555555175 in main ()
at /tmp/amd64-shadow-stack-signal.c:29
~~~
---
gdb/amd64-linux-tdep.c | 36 +++++++++++--------
gdb/gdbarch-gen.c | 32 +++++++++++++++++
gdb/gdbarch-gen.h | 11 ++++++
gdb/gdbarch_components.py | 13 +++++++
gdb/shadow-stack.c | 11 ++++++
gdb/shadow-stack.h | 5 +++
.../amd64-shadow-stack-backtrace-signal.exp | 2 +-
7 files changed, 95 insertions(+), 15 deletions(-)
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index d98415b8989..ab3b65ed4b8 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -147,12 +147,10 @@ static const gdb_byte amd64_x32_linux_sigtramp_code[] =
the routine. Otherwise, return 0. */
static CORE_ADDR
-amd64_linux_sigtramp_start (const frame_info_ptr &this_frame)
+amd64_linux_sigtramp_start (gdbarch *gdbarch, CORE_ADDR pc)
{
- struct gdbarch *gdbarch;
const gdb_byte *sigtramp_code;
- CORE_ADDR pc = get_frame_pc (this_frame);
- gdb_byte buf[LINUX_SIGTRAMP_LEN];
+ std::array<gdb_byte, LINUX_SIGTRAMP_LEN> buf {};
/* We only recognize a signal trampoline if PC is at the start of
one of the two instructions. We optimize for finding the PC at
@@ -161,7 +159,7 @@ amd64_linux_sigtramp_start (const frame_info_ptr &this_frame)
PC is not at the start of the instruction sequence, there will be
a few trailing readable bytes on the stack. */
- if (!safe_frame_unwind_memory (this_frame, pc, buf))
+ if (target_read_memory (pc, buf.data (), buf.size ()) != 0)
return 0;
if (buf[0] != LINUX_SIGTRAMP_INSN0)
@@ -170,28 +168,25 @@ amd64_linux_sigtramp_start (const frame_info_ptr &this_frame)
return 0;
pc -= LINUX_SIGTRAMP_OFFSET1;
- if (!safe_frame_unwind_memory (this_frame, pc, buf))
+ if (target_read_memory (pc, buf.data (), buf.size ()) != 0)
return 0;
}
- gdbarch = get_frame_arch (this_frame);
if (gdbarch_ptr_bit (gdbarch) == 32)
sigtramp_code = amd64_x32_linux_sigtramp_code;
else
sigtramp_code = amd64_linux_sigtramp_code;
- if (memcmp (buf, sigtramp_code, LINUX_SIGTRAMP_LEN) != 0)
+ if (memcmp (buf.data (), sigtramp_code, LINUX_SIGTRAMP_LEN) != 0)
return 0;
return pc;
}
-/* Return whether THIS_FRAME corresponds to a GNU/Linux sigtramp
- routine. */
+/* Return whether PC is pointing to a GNU/Linux sigtramp routine. */
static int
-amd64_linux_sigtramp_p (const frame_info_ptr &this_frame)
+amd64_linux_is_sigtramp_pc (gdbarch* gdbarch, const CORE_ADDR pc)
{
- CORE_ADDR pc = get_frame_pc (this_frame);
const char *name;
find_pc_partial_function (pc, &name, NULL, NULL);
@@ -203,11 +198,22 @@ amd64_linux_sigtramp_p (const frame_info_ptr &this_frame)
__sigaction, or __libc_sigaction (all aliases to the same
function). */
if (name == NULL || strstr (name, "sigaction") != NULL)
- return (amd64_linux_sigtramp_start (this_frame) != 0);
+ return (amd64_linux_sigtramp_start (gdbarch, pc) != 0);
return (streq ("__restore_rt", name));
}
+/* Return whether THIS_FRAME corresponds to a GNU/Linux sigtramp
+ routine. */
+
+static int
+amd64_linux_sigtramp_frame_p (const frame_info_ptr &this_frame)
+{
+ gdbarch *gdbarch = get_frame_arch (this_frame);
+ CORE_ADDR pc = get_frame_pc (this_frame);
+ return amd64_linux_is_sigtramp_pc (gdbarch, pc);
+}
+
/* Offset to struct sigcontext in ucontext, from <asm/ucontext.h>. */
#define AMD64_LINUX_UCONTEXT_SIGCONTEXT_OFFSET 40
@@ -2024,7 +2030,7 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
linux_init_abi (info, gdbarch, num_disp_step_buffers);
- tdep->sigtramp_p = amd64_linux_sigtramp_p;
+ tdep->sigtramp_p = amd64_linux_sigtramp_frame_p;
tdep->sigcontext_addr = amd64_linux_sigcontext_addr;
tdep->sc_reg_offset = amd64_linux_sc_reg_offset;
tdep->sc_num_regs = ARRAY_SIZE (amd64_linux_sc_reg_offset);
@@ -2082,6 +2088,8 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
set_gdbarch_is_no_return_shadow_stack_address
(gdbarch, amd64_linux_is_no_return_shadow_stack_address);
+
+ set_gdbarch_is_sigtramp_pc (gdbarch, amd64_linux_is_sigtramp_pc);
}
static void
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index e93afff34b1..c0b882563c1 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -257,6 +257,7 @@ struct gdbarch
gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr;
int shadow_stack_element_size_aligned = 8;
gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address = nullptr;
+ gdbarch_is_sigtramp_pc_ftype *is_sigtramp_pc = nullptr;
};
/* Create a new ``struct gdbarch'' based on information provided by
@@ -521,6 +522,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
/* Skip verify of top_addr_empty_shadow_stack, has predicate. */
/* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0. */
/* Skip verify of is_no_return_shadow_stack_address, has predicate. */
+ /* Skip verify of is_sigtramp_pc, has predicate. */
if (!log.empty ())
internal_error (_("verify_gdbarch: the following are invalid ...%s"),
log.c_str ());
@@ -1365,6 +1367,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
gdb_printf (file,
"gdbarch_dump: is_no_return_shadow_stack_address = <%s>\n",
host_address_to_string (gdbarch->is_no_return_shadow_stack_address));
+ gdb_printf (file,
+ "gdbarch_dump: gdbarch_is_sigtramp_pc_p() = %d\n",
+ gdbarch_is_sigtramp_pc_p (gdbarch));
+ gdb_printf (file,
+ "gdbarch_dump: is_sigtramp_pc = <%s>\n",
+ host_address_to_string (gdbarch->is_sigtramp_pc));
if (gdbarch->dump_tdep != nullptr)
gdbarch->dump_tdep (gdbarch, file);
}
@@ -5394,3 +5402,27 @@ set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch,
{
gdbarch->is_no_return_shadow_stack_address = is_no_return_shadow_stack_address;
}
+
+bool
+gdbarch_is_sigtramp_pc_p (struct gdbarch *gdbarch)
+{
+ gdb_assert (gdbarch != nullptr);
+ return gdbarch->is_sigtramp_pc != nullptr;
+}
+
+int
+gdbarch_is_sigtramp_pc (struct gdbarch *gdbarch, const CORE_ADDR pc)
+{
+ gdb_assert (gdbarch != nullptr);
+ gdb_assert (gdbarch->is_sigtramp_pc != nullptr);
+ if (gdbarch_debug >= 2)
+ gdb_printf (gdb_stdlog, "gdbarch_is_sigtramp_pc called\n");
+ return gdbarch->is_sigtramp_pc (gdbarch, pc);
+}
+
+void
+set_gdbarch_is_sigtramp_pc (struct gdbarch *gdbarch,
+ gdbarch_is_sigtramp_pc_ftype is_sigtramp_pc)
+{
+ gdbarch->is_sigtramp_pc = is_sigtramp_pc;
+}
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index 791324777dd..31500ee104d 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1808,3 +1808,14 @@ bool gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch);
using gdbarch_is_no_return_shadow_stack_address_ftype = std::optional<shadow_stack_frame_info> (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level);
std::optional<shadow_stack_frame_info> gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level);
void set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address);
+
+/* Return whether PC is pointing to a sigtramp routine. This is needed in
+ addition to tdep->sigtramp_p, which requires a frame, because some callers
+ (e.g. the shadow stack backtrace command) only have a return address
+ available. */
+
+bool gdbarch_is_sigtramp_pc_p (struct gdbarch *gdbarch);
+
+using gdbarch_is_sigtramp_pc_ftype = int (struct gdbarch *gdbarch, const CORE_ADDR pc);
+int gdbarch_is_sigtramp_pc (struct gdbarch *gdbarch, const CORE_ADDR pc);
+void set_gdbarch_is_sigtramp_pc (struct gdbarch *gdbarch, gdbarch_is_sigtramp_pc_ftype *is_sigtramp_pc);
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index 0435b1b5eca..fce705c33a1 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2864,3 +2864,16 @@ backtrace. Otherwise, return an empty optional.
],
predicate=True,
)
+
+Method(
+ comment="""
+Return whether PC is pointing to a sigtramp routine. This is needed in
+addition to tdep->sigtramp_p, which requires a frame, because some callers
+(e.g. the shadow stack backtrace command) only have a return address
+available.
+""",
+ type="int",
+ name="is_sigtramp_pc",
+ params=[("const CORE_ADDR", "pc")],
+ predicate=True,
+)
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 94062034bd3..6515d4b182d 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -267,6 +267,8 @@ do_print_shadow_stack_frame_info
}
else if (frame.type == ssp_frame_type::dummy_frame)
str = "<function called from gdb>";
+ else if (frame.type == ssp_frame_type::sigtramp_frame)
+ str = "<signal handler called>";
else
gdb_assert_not_reached ("Invalid shadow stack frame type.");
@@ -480,6 +482,15 @@ get_shadow_stack_frame_info
if (sal_arch == nullptr)
sal_arch = fallback_arch;
+ if (gdbarch_is_sigtramp_pc_p (sal_arch)
+ && gdbarch_is_sigtramp_pc (sal_arch, value))
+ {
+ return std::optional<shadow_stack_frame_info>
+ ({ssp, value, level, sal_arch, sal,
+ ssp_frame_type::sigtramp_frame, {},
+ ssp_unwind_stop_reason::no_error});
+ }
+
return std::optional<shadow_stack_frame_info>
({ssp, value, level, sal_arch, sal,
ssp_frame_type::normal_frame, {},
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 225fd3996cd..d4b0d53acf3 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -80,6 +80,11 @@ enum class ssp_frame_type
/* A fake frame, created by GDB when performing an inferior function
call. */
dummy_frame,
+
+ /* A shadow stack frame containing a return address which is in a signal
+ handler, similar to the SIGTRAMP_FRAME frame type in
+ frame.h:frame_type. */
+ sigtramp_frame,
};
/* Information of a shadow stack frame belonging to a shadow stack element
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
index 21373dc07f3..34559f0bd04 100644
--- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
@@ -41,7 +41,7 @@ save_vars { ::env(GLIBC_TUNABLES) } {
# Test shadow stack backtrace including <sigframe token>.
gdb_test "bt -shadow" \
[multi_line \
- "#0\[ \t\]*$hex in \[^\r\n\]+" \
+ "#0\[ \t\]*<signal handler called>" \
"#1\[ \t\]*<sigframe token>" \
"#2\[ \t\]*$hex in \[^\r\n\]+" \
".*" ] \
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v4 13/13] gdb, mi: Add -shadow-stack-list-frames command
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
` (11 preceding siblings ...)
2026-07-08 14:36 ` [PATCH v4 12/13] gdb: Enable signal trampolines " Christina Schimpe
@ 2026-07-08 14:36 ` Christina Schimpe
12 siblings, 0 replies; 14+ messages in thread
From: Christina Schimpe @ 2026-07-08 14:36 UTC (permalink / raw)
To: gdb-patches; +Cc: tom, thiago.bauermann
Add the mi command for the command "backtrace -shadow".
Similar to the mi interface for the ordinary backtrace command,
support low-frame and high-frame as command line parameters.
Example print of a full shadow stack backtrace:
~~~
(gdb)
-shadow-stack-list-frames
^done,shadow-stack=[
shadow-stack-frame={level="0",addr="0x00007ffff7c3fe70",
func="__libc_start_call_main",file="../sysdeps/nptl/libc_start_call_main.h",
fullname="/usr/[...]/sysdeps/nptl/libc_start_call_main.h",
line="58",arch="i386:x86-64"},
shadow-stack-frame={level="1",addr="0x00007ffff7c3ff20",
func="__libc_start_main_impl",file="../csu/libc-start.c",
fullname="/usr/[...]/csu/libc-start.c",
line="128",arch="i386:x86-64"},
shadow-stack-frame={level="2",addr="0x0000000000401075",
func="_start",arch="i386:x86-64"}]
~~~
Example print of a shadow stack backtrace using low- and high-frame:
~~~
(gdb)
-shadow-stack-list-frames 0 1
^done,shadow-stack=[
shadow-stack-frame={level="0",addr="0x00007ffff7c3fe70",
func="__libc_start_call_main",file="../sysdeps/nptl/libc_start_call_main.h",
fullname="/usr/[...]/sysdeps/nptl/libc_start_call_main.h",
line="58",arch="i386:x86-64"},
shadow-stack-frame={level="1",addr="0x00007ffff7c3ff20",
func="__libc_start_main_impl",file="../csu/libc-start.c",
fullname="/usr/[...]/csu/libc-start.c",
line="128",arch="i386:x86-64"}]
~~~
---
gdb/NEWS | 8 ++
gdb/doc/gdb.texinfo | 51 ++++++++
gdb/mi/mi-cmd-stack.c | 100 +++++++++++++++
gdb/mi/mi-cmds.c | 2 +
gdb/mi/mi-cmds.h | 1 +
gdb/shadow-stack.c | 121 ++++++++++++------
gdb/shadow-stack.h | 40 ++++++
.../gdb.mi/mi-shadow-stack-signal.exp | 69 ++++++++++
gdb/testsuite/gdb.mi/mi-shadow-stack.exp | 93 ++++++++++++++
9 files changed, 449 insertions(+), 36 deletions(-)
create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index fa06d21ddac..8d45ec373f2 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -333,6 +333,14 @@ qExecAndArgs
AIX version is now AIX 7.2 TL5. GDB will only support
DWARF debugging in AIX, going forward.
+* New MI commands
+
+-shadow-stack-list-frames
+ Added new MI command which is equivalent to the CLI command
+ 'backtrace -shadow' but supports 'low-frame' and 'high-frame' as
+ command line parameters. The parameters are used to print shadow
+ stack frames between between certain levels on the shadow stack only.
+
*** Changes in GDB 17
* Debugging Linux programs that use x86-64 or x86-64 with 32-bit pointer
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 665576c4fc4..0f48709fc44 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -35532,6 +35532,57 @@ Show a single frame:
(gdb)
@end smallexample
+@anchor{-shadow-stack-list-frames}
+@findex -shadow-stack-list-frames
+@subheading The @code{-shadow-stack-list-frames} Command
+
+@subsubheading Synopsis
+
+@smallexample
+ -shadow-stack-list-frames [ @var{low-frame} @var{high-frame} ]
+@end smallexample
+
+List the shadow stack frames currently on the shadow stack. In case the
+element on the shadow stack is a return address, @value{GDBN} prints the
+same fields as in @code{-stack-list-frames} with the return address on
+the shadow stack as @var{addr}.
+If the element on the shadow stack is not a return address, @value{GDBN}
+only prints @var{level}, @var{addr} and @var{arch}.
+
+If invoked without arguments, this command prints a backtrace for the
+whole shadow stack. Like the @code{-stack-list-frames} command, if given
+two integer arguments, it shows the frames whose levels are between the
+two arguments (inclusive).
+
+@subsubheading @value{GDBN} Command
+
+The corresponding @value{GDBN} command is @samp{backtrace -shadow}.
+
+@subsubheading Example
+
+Show shadow stack frames between @var{low-frame} and @var{high-frame}:
+
+@smallexample
+(gdb)
+-shadow-stack-list-frames 0 2
+^done,shadow-stack=[
+shadow-stack-frame=@{level="0",addr="0x00007ffff7c45330",
+ func="<signal handler called>",arch="i386:x86-64"@},
+shadow-stack-frame=@{level="1",addr="0x80007ffff7bfffd8",
+ func="<sigframe token>",arch="i386:x86-64"@},
+shadow-stack-frame=@{level="2",addr="0x00007ffff7c4527e",
+ func="__GI_raise",file="[...]/raise.c",
+ fullname="./[...]/posix/raise.c",
+ line="26",arch="i386:x86-64"@}]
+(gdb)
+@end smallexample
+
+Note that for frame 0 and 1, @value{GDBN} printed only @var{level},
+@var{addr}, @var{func} and @var{arch}. For frame 0, @var{func} is
+@code{"<signal handler called>"} similar to the @code{-stack-list-frames}
+command. For frame 1 @value{GDBN} prints @code{"<sigframe token>"} for
+@var{func}. This is due to an element on the shadow stack which is not a
+return address.
@findex -stack-list-locals
@anchor{-stack-list-locals}
diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c
index 280a08d121e..e6948838da4 100644
--- a/gdb/mi/mi-cmd-stack.c
+++ b/gdb/mi/mi-cmd-stack.c
@@ -32,6 +32,9 @@
#include "mi-parse.h"
#include <optional>
#include "inferior.h"
+#include "shadow-stack.h"
+#include "gdbarch.h"
+#include "arch-utils.h"
enum what_to_list { locals, arguments, all };
@@ -764,3 +767,100 @@ mi_cmd_stack_info_frame (const char *command, const char *const *argv,
print_frame_info (user_frame_print_options,
get_selected_frame (), 1, LOC_AND_ADDRESS, 0, 1);
}
+
+/* Parse arguments of -shadow-stack-list-frames command and set FRAME_LOW
+ and FRAME_HIGH accordingly. Throw an error in case the arguments are
+ invalid. */
+static void
+mi_cmd_shadow_stack_list_frames_parse_args (const char *const *argv,
+ int argc, int &frame_low,
+ int &frame_high)
+{
+ const std::string mi_cmd_name = "-shadow-stack-list-frames";
+ /* There should either be low - high range, or no arguments. */
+ if ((argc != 0) && (argc != 2))
+ error (_("%s: Usage: [FRAME_LOW FRAME_HIGH]"), mi_cmd_name.c_str ());
+
+ /* If there is a range, set it. */
+ if (argc == 2)
+ {
+ frame_low = atoi (argv[0]);
+ frame_high = atoi (argv[1]);
+ std::string err_str;
+ if (frame_low < 0)
+ {
+ err_str = "``" + std::to_string (frame_low) + "''";
+ if (frame_high < 0)
+ err_str += " and ``" + std::to_string (frame_high) + "''";
+ }
+ else if (frame_high < 0)
+ err_str = "``" + std::to_string (frame_high) + "''";
+
+ if (!err_str.empty ())
+ {
+ err_str = mi_cmd_name + ": Invalid option " + err_str + ".";
+ error (_("%s"), err_str.c_str ());
+ }
+ }
+ else
+ {
+ /* No arguments, print the whole shadow stack backtrace. */
+ frame_low = -1;
+ frame_high = -1;
+ }
+}
+
+/* Print a list of the shadow stack frames. Args can be none, in which
+ case we want to print the whole shadow stack backtrace, or a pair of
+ numbers specifying the frame numbers at which to start and stop the
+ display. If the two numbers are equal, a single frame will be
+ displayed. */
+
+void
+mi_cmd_shadow_stack_list_frames (const char *command,
+ const char *const *argv,
+ int argc)
+{
+ int frame_low;
+ int frame_high;
+
+ mi_cmd_shadow_stack_list_frames_parse_args (argv, argc, frame_low,
+ frame_high);
+
+ gdbarch *gdbarch = get_current_arch ();
+ const CORE_ADDR start_ssp
+ = get_validated_shadow_stack_pointer (gdbarch);
+
+ ui_out_emit_list list_emitter (current_uiout, "shadow-stack");
+
+ std::optional<std::pair<CORE_ADDR, CORE_ADDR>> range
+ = get_non_empty_shadow_stack_range (gdbarch, start_ssp);
+ if (!range.has_value ())
+ return;
+
+ /* Extract the first shadow stack frame info (level 0). */
+ std::optional<shadow_stack_frame_info> curr
+ = get_shadow_stack_frame_info (gdbarch, start_ssp, 0);
+
+ /* Let's position curr on the shadow stack frame at which to start the
+ display. This could be the innermost frame if the whole shadow stack
+ needs displaying, or if frame_low is 0. */
+ int frame_num = 0;
+ for (; curr.has_value () && frame_num < frame_low; frame_num++)
+ curr = curr->unwind_prev_shadow_stack_frame_info (*range);
+
+ if (!curr.has_value ())
+ error (_("-shadow-stack-list-frames: Not enough frames on the shadow "
+ "stack."));
+
+ /* Now let's print the shadow stack frames up to frame_high, or until
+ the bottom of the shadow stack. */
+ for (; curr.has_value () && (frame_num <= frame_high || frame_high == -1);
+ frame_num++)
+ {
+ QUIT;
+ print_shadow_stack_frame_info (user_frame_print_options, *curr,
+ LOCATION);
+ curr = curr->unwind_prev_shadow_stack_frame_info (*range);
+ }
+}
diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c
index 48d85e8785c..51f173f66ee 100644
--- a/gdb/mi/mi-cmds.c
+++ b/gdb/mi/mi-cmds.c
@@ -303,6 +303,8 @@ add_builtin_mi_commands ()
add_mi_cmd_mi ("stack-info-frame", mi_cmd_stack_info_frame);
add_mi_cmd_mi ("stack-list-arguments", mi_cmd_stack_list_args);
add_mi_cmd_mi ("stack-list-frames", mi_cmd_stack_list_frames);
+ add_mi_cmd_mi ("shadow-stack-list-frames",
+ mi_cmd_shadow_stack_list_frames);
add_mi_cmd_mi ("stack-list-locals", mi_cmd_stack_list_locals);
add_mi_cmd_mi ("stack-list-variables", mi_cmd_stack_list_variables);
add_mi_cmd_mi ("stack-select-frame", mi_cmd_stack_select_frame,
diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h
index 81c4f0b4e37..2a37bd8861a 100644
--- a/gdb/mi/mi-cmds.h
+++ b/gdb/mi/mi-cmds.h
@@ -100,6 +100,7 @@ extern mi_cmd_argv_ftype mi_cmd_stack_list_frames;
extern mi_cmd_argv_ftype mi_cmd_stack_list_locals;
extern mi_cmd_argv_ftype mi_cmd_stack_list_variables;
extern mi_cmd_argv_ftype mi_cmd_stack_select_frame;
+extern mi_cmd_argv_ftype mi_cmd_shadow_stack_list_frames;
extern mi_cmd_argv_ftype mi_cmd_symbol_list_lines;
extern mi_cmd_argv_ftype mi_cmd_symbol_info_functions;
extern mi_cmd_argv_ftype mi_cmd_symbol_info_module_functions;
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 6515d4b182d..12d44a497a6 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -276,8 +276,21 @@ do_print_shadow_stack_frame_info
uiout->text ("#");
uiout->field_fmt_signed (2, ui_left, "level", frame.level);
+ if (uiout->is_mi_like_p ())
+ {
+ uiout->field_string
+ ("addr", hex_string_custom (frame.value, element_size * 2),
+ address_style.style ());
+ }
+
uiout->field_string ("func", str, metadata_style.style ());
+ if (uiout->is_mi_like_p ())
+ {
+ uiout->field_string
+ ("arch", gdbarch_bfd_arch_info (frame.arch)->printable_name);
+ }
+
uiout->text ("\n");
gdb_flush (gdb_stdout);
return;
@@ -323,6 +336,12 @@ do_print_shadow_stack_frame_info
print_lib (uiout, lib);
}
+ if (uiout->is_mi_like_p ())
+ {
+ uiout->field_string
+ ("arch", gdbarch_bfd_arch_info (frame.arch)->printable_name);
+ }
+
uiout->text ("\n");
}
@@ -342,10 +361,9 @@ do_print_shadow_stack_frame_info
gdb_flush (gdb_stdout);
}
-/* Redirect output to a temporary buffer for the duration of
- do_print_shadow_stack_frame_info. */
+/* See shadow-stack.h. */
-static void
+void
print_shadow_stack_frame_info
(const frame_print_options &fp_opts,
const shadow_stack_frame_info &frame,
@@ -372,11 +390,9 @@ ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
gdb_assert_not_reached ("invalid unwind stop reason.");
}
-/* Read the memory at shadow stack pointer SSP and assign it to
- RETURN_VALUE. In case of success, return true. Otherwise,
- return false. */
+/* See shadow-stack.h. */
-static bool
+bool
read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
CORE_ADDR &return_value)
{
@@ -435,12 +451,9 @@ get_trailing_outermost_shadow_stack_frame_info
return trailing;
}
-/* If possible, get shadow stack frame info for the shadow stack pointer
- SSP and its current frame LEVEL. Pass FALLBACK_ARCH which can be used
- as fallback gdbarch in case the gdbarch cannot be extracted from the
- SAL. Usually this is the gdbarch of the previous frame. */
+/* See shadow-stack.h. */
-static std::optional<shadow_stack_frame_info>
+std::optional<shadow_stack_frame_info>
get_shadow_stack_frame_info
(gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level)
{
@@ -596,22 +609,30 @@ shadow_stack_frame_info::inside_main_func () const
return false;
}
-/* Print all elements on the shadow stack or just the innermost COUNT_EXP
- frames. */
+/* See shadow-stack.h. */
-void
-backtrace_shadow_command (const frame_print_options &fp_opts,
- const char *count_exp, int from_tty)
+CORE_ADDR
+get_validated_shadow_stack_pointer (gdbarch *gdbarch)
{
if (!target_has_stack ())
- error (_("No shadow stack."));
+ {
+ if (!current_uiout->is_mi_like_p ())
+ error (_("No shadow stack."));
+ else
+ error (_("-shadow-stack-list-frames: No shadow stack."));
+ }
- gdbarch *gdbarch = get_current_arch ();
if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
|| !gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
|| gdbarch_ssp_regnum (gdbarch) == -1)
- error (_("Printing of the shadow stack backtrace is not supported for"
- " the current target."));
+ {
+ std::string err_str = "Printing of the shadow stack backtrace";
+ err_str += " is not supported for the current target.";
+ if (!current_uiout->is_mi_like_p ())
+ error (_("%s"), err_str.c_str());
+ else
+ error (_("-shadow-stack-list-frames: %s"), err_str.c_str());
+ }
regcache *regcache = get_thread_regcache (inferior_thread ());
bool shadow_stack_enabled = false;
@@ -621,10 +642,25 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
shadow_stack_enabled);
if (!start_ssp.has_value () || !shadow_stack_enabled)
- error (_("Shadow stack is not enabled for the current thread."));
+ {
+ std::string err_str
+ = "Shadow stack is not enabled for the current thread.";
+ if (!current_uiout->is_mi_like_p ())
+ error (_("%s"), err_str.c_str());
+ else
+ error (_("-shadow-stack-list-frames: %s"), err_str.c_str());
+ }
- /* Check if START_SSP points to a shadow stack memory range and use
- the returned range to determine when to stop unwinding.
+ return *start_ssp;
+}
+
+/* See shadow-stack.h. */
+
+std::optional<std::pair<CORE_ADDR, CORE_ADDR>>
+get_non_empty_shadow_stack_range (gdbarch *gdbarch, const CORE_ADDR ssp)
+{
+ /* Check if SSP points to a shadow stack memory range and use the
+ returned range to determine when to stop unwinding.
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.
@@ -632,26 +668,39 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
Linux kernel v6.6. However, shadow stack switches are not supported
due to missing kernel space support. We therefore implement this
command without support for shadow stack switches for now. */
- bool is_top_addr_empty_shadow_stack = false;
std::pair<CORE_ADDR, CORE_ADDR> range;
- if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
- &range))
+ if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, ssp, &range))
{
/* For x86, if the current shadow stack pointer does not point to
shadow stack memory but is valid, the shadow stack is empty. */
- is_top_addr_empty_shadow_stack = true;
+ return {};
}
- if (!is_top_addr_empty_shadow_stack)
+ if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
+ && gdbarch_top_addr_empty_shadow_stack (gdbarch, ssp, range))
{
/* For ARM's Guarded Control Stack, the shadow stack can be empty
even though START_SSP points to shadow stack memory range. */
- is_top_addr_empty_shadow_stack
- = gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
- && gdbarch_top_addr_empty_shadow_stack (gdbarch, *start_ssp, range);
+ return {};
}
- if (is_top_addr_empty_shadow_stack)
+ return {range};
+}
+
+/* Print all elements on the shadow stack or just the innermost COUNT_EXP
+ frames. */
+
+void
+backtrace_shadow_command (const frame_print_options &fp_opts,
+ const char *count_exp, int from_tty)
+{
+ gdbarch *gdbarch = get_current_arch ();
+ const CORE_ADDR start_ssp
+ = get_validated_shadow_stack_pointer (gdbarch);
+
+ std::optional<std::pair<CORE_ADDR, CORE_ADDR>> range
+ = get_non_empty_shadow_stack_range (gdbarch, start_ssp);
+ if (!range.has_value ())
{
gdb_printf (_("The shadow stack is empty.\n"));
return;
@@ -660,7 +709,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
/* Extract the first shadow stack frame info (level 0). */
ssp_unwind_stop_reason reason = ssp_unwind_stop_reason::no_error;
std::optional<shadow_stack_frame_info> current
- = get_shadow_stack_frame_info (gdbarch, *start_ssp, 0);
+ = get_shadow_stack_frame_info (gdbarch, start_ssp, 0);
if (!current.has_value ())
reason = ssp_unwind_stop_reason::memory_read_error;
@@ -676,7 +725,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
if (count < 0)
{
trailing = get_trailing_outermost_shadow_stack_frame_info
- (range, std::abs (count), *current);
+ (*range, std::abs (count), *current);
if (!trailing.has_value ())
reason = current->unwind_stop_reason;
@@ -698,7 +747,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
print_shadow_stack_frame_info (fp_opts, *current, LOCATION);
trailing = current;
- current = current->unwind_prev_shadow_stack_frame_info (range);
+ current = current->unwind_prev_shadow_stack_frame_info (*range);
}
/* If we've stopped before the end, mention that. */
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index d4b0d53acf3..4eb8c53f995 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -139,4 +139,44 @@ class shadow_stack_frame_info
bool inside_main_func () const;
};
+/* Read the memory at shadow stack pointer SSP and assign it to
+ RETURN_VALUE. In case of success, return true. Otherwise, return
+ false. */
+
+bool read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
+ CORE_ADDR &return_value);
+
+/* Print information of shadow stack frame info FRAME. The output is
+ formatted according to PRINT_WHAT. For the meaning of PRINT_WHAT, see
+ enum print_what comments in frame.h. Note that PRINT_WHAT is
+ overridden, if PRINT_OPTIONS.print_frame_info != print_frame_info_auto.
+ Redirect output to a temporary buffer for the duration of
+ do_print_shadow_stack_frame_info. */
+
+void print_shadow_stack_frame_info
+ (const frame_print_options &fp_opts,
+ const shadow_stack_frame_info &frame,
+ print_what print_what);
+
+/* Check if shadow stacks are supported and enabled. If it is, return the
+ validated shadow stack pointer. If not, throw an appropriate error
+ message, either for the CLI command "bt -shadow" or for the MI equivalent
+ -shadow-stack-list-frames. */
+
+CORE_ADDR get_validated_shadow_stack_pointer (gdbarch *gdbarch);
+
+/* If the shadow stack pointed to by SSP is empty, return an empty optional.
+ Otherwise return the shadow stack memory range. */
+
+std::optional<std::pair<CORE_ADDR, CORE_ADDR>>
+ get_non_empty_shadow_stack_range (gdbarch *gdbarch, const CORE_ADDR ssp);
+
+/* If possible, get shadow stack frame info for the shadow stack pointer
+ SSP and its current frame LEVEL. Pass FALLBACK_ARCH which can be used
+ as fallback gdbarch in case the gdbarch cannot be extracted from the
+ SAL. Usually this is the gdbarch of the previous frame. */
+
+std::optional<shadow_stack_frame_info> get_shadow_stack_frame_info
+ (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level);
+
#endif /* GDB_SHADOW_STACK_H */
diff --git a/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
new file mode 100644
index 00000000000..cf302ae1a45
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
@@ -0,0 +1,69 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test the mi command -shadow-stack-list-frames for signal handling on linux.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+require allow_ssp_tests {istarget "*-*-linux*"}
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+ append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+ set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack-signal.c"
+ set testfile mi-shadow-stack
+
+ # Test shadow-stack-list-frames for shadow stack element which is no
+ # return address.
+ if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+ {debug additional_flags="-fcf-protection=return"}] } {
+ return
+ }
+
+ if { [mi_clean_restart $testfile] } {
+ return
+ }
+
+ mi_runto_main
+ mi_send_resuming_command "exec-continue" "continue till signal"
+
+ set r_signal "reason=\"signal-received\",signal-name=\"SIGUSR1\",signal-meaning=\"User defined signal 1\""
+ gdb_expect {
+ -re ".*stopped,${r_signal}.*$mi_gdb_prompt" {
+ pass "Wait for user interrupt"
+ }
+ timeout {
+ fail "Wait for user interrupt (timeout)"
+ return
+ }
+ }
+
+ mi_gdb_test "break handler" \
+ {(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done}
+
+ mi_execute_to "exec-continue" "breakpoint-hit" "handler" ".*" ".*" ".*" \
+ {"" "disp=\"keep\""} "continue to handler"
+
+ # We only test the frame belonging to the shadow stack element which
+ # is not a return address. This frame is triggered by the signal
+ # exception.
+ set any "\[^\"\]+"
+ mi_gdb_test "231-shadow-stack-list-frames 1 1" \
+ "231\\^done,shadow-stack=\\\[shadow-stack-frame=\{level=\"1\",addr=\"$hex\",func=\"<sigframe token>\",arch=\"$any\"\}\\\]" \
+ "test shadow-stack-list-frames"
+
+ mi_gdb_exit
+}
diff --git a/gdb/testsuite/gdb.mi/mi-shadow-stack.exp b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
new file mode 100644
index 00000000000..ac045d6f3a2
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
@@ -0,0 +1,93 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test the mi command -shadow-stack-list-frames.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+require allow_ssp_tests
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+ append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+ set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack.c"
+ set testfile mi-shadow-stack
+
+ if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+ {debug additional_flags="-fcf-protection=return"}] } {
+ return
+ }
+
+ if { [mi_clean_restart $testfile] } {
+ return
+ }
+
+ mi_runto_main
+
+ mi_gdb_test "break call2" \
+ {(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done}
+
+ mi_execute_to "exec-continue" "breakpoint-hit" "call2" ".*" ".*" ".*" \
+ {"" "disp=\"keep\""} "continue to call2"
+
+ set any "\[^\"\]+"
+ set any_remaining_frame_attr "\[^\r\n]+"
+
+ # It's enough to test the first 3 frames. For frame 3 we just test that it
+ # exists as other attributes might depend on the environment.
+ set frame_start "shadow-stack-frame=\{level="
+ set frame1 "$frame_start\"0\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}"
+ set frame2 "$frame_start\"1\",addr=\"$hex\",func=\"main\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}"
+ set frame3 "$frame_start\"2\",addr=\"$hex\"$any_remaining_frame_attr\}"
+ mi_gdb_test "231-shadow-stack-list-frames" \
+ "231\\^done,shadow-stack=\\\[$frame1.*$frame2\\\]" \
+ "test shadow-stack-list-frames"
+
+ mi_gdb_test "set backtrace past-main on" \
+ ".*=cmd-param-changed,param=\"backtrace past-main\",value=\"on\".*\\^done"
+ mi_gdb_test "231-shadow-stack-list-frames" \
+ "231\\^done,shadow-stack=\\\[$frame1.*$frame2.*$frame3.*\\\]" \
+ "test shadow-stack-list-frames past main"
+
+ # Test low-frame/high-frame
+ mi_gdb_test "231-shadow-stack-list-frames 0 1" \
+ "231\\^done,shadow-stack=\\\[$frame1.*$frame2\\\]" \
+ "test shadow-stack-list-frames low/high-frames"
+
+ # Test inferior function calls.
+ set inside_infcall_str "The program being debugged stopped while in a function called from GDB"
+ mi_gdb_test "call (int) call2()" \
+ ".*Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" \
+ "call (int) call2()"
+
+ set dummy_frame1 "$frame_start\"0\",addr=\"$hex\",func=\"<function called from gdb>\",arch=\"$any\"\}"
+ set frame1 "$frame_start\"1\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}"
+ mi_gdb_test "231-shadow-stack-list-frames" \
+ "231\\^done,shadow-stack=\\\[$dummy_frame1.*$frame1.*" \
+ "test shadow-stack-list-frames inside inferior call"
+
+ mi_gdb_test "call (int) call2()" \
+ ".*Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" \
+ "Execute an inferior call inside an inferior call."
+
+ set dummy_frame2 "$frame_start\"1\",addr=\"$hex\",func=\"<function called from gdb>\",arch=\"$any\"\}"
+ set frame2 "$frame_start\"2\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}"
+ mi_gdb_test "231-shadow-stack-list-frames" \
+ "231\\^done,shadow-stack=\\\[$dummy_frame1.*$dummy_frame2.*$frame2.*" \
+ "test shadow-stack-list-frames inside nested inferior call"
+
+ mi_gdb_exit
+}
--
2.34.1
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
^ permalink raw reply [flat|nested] 14+ messages in thread