Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH 0/9] Add new command to print the shadow stack backtrace
@ 2025-09-23 11:18 Christina Schimpe
  2025-09-23 11:18 ` [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
                   ` (9 more replies)
  0 siblings, 10 replies; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

Hi all,

as already indicated in the discussion
https://sourceware.org/pipermail/gdb/2023-December/051024.html
I'd like to add a command to print the shadow stack backtrace.

In the discussion above the main conclusion was to use the ordinary
backtrace command with a new flag: "bt -shadow".
I chose a similar direction, but decided against the flag "-shadow"
in favor of a new subcommand of the ordinary backtrace command:
"bt shadow".

This is an example shadow stack backtrace on amd64:
~~~
(gdb) bt shadow
#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
#1  0x000000000040112f in main at amd64-shadow-stack.c:21
#2  0x00007ffff7c3fe70 in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
#3  0x00007ffff7c3ff20 in __libc_start_main_impl at ../csu/libc-start.c:128
#4  0x0000000000401045 in _start
~~~

I am happy about your feedback!

Christina


Christina Schimpe (9):
  gdb: Generalize handling of the shadow stack pointer.
  gdb: Refactor 'stack.c:print_frame'.
  gdb: Introduce 'stack.c:print_pc' function without frame argument.
  gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in
    stack.c.
  gdb: Refactor 'stack.c:print_frame_info'.
  gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  gdb: Provide gdbarch hook to distinguish shadow stack backtrace
    elements.
  gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64
    linux.
  gdb, mi: Add -shadow-stack-list-frames command

 gdb/Makefile.in                               |   2 +
 gdb/NEWS                                      |  17 +
 gdb/aarch64-linux-tdep.c                      |  51 +-
 gdb/aarch64-tdep.c                            |  42 +-
 gdb/amd64-linux-tdep.c                        | 164 ++---
 gdb/amd64-tdep.c                              |  20 +
 gdb/annotate.c                                |  93 ++-
 gdb/annotate.h                                |  18 +-
 gdb/doc/gdb.texinfo                           | 114 +++
 gdb/gdbarch-gen.c                             | 158 ++++-
 gdb/gdbarch-gen.h                             |  80 ++-
 gdb/gdbarch.h                                 |   1 +
 gdb/gdbarch_components.py                     | 100 ++-
 gdb/infcall.c                                 |   4 +-
 gdb/linux-tdep.c                              |   2 +
 gdb/mi/mi-cmd-stack.c                         | 146 ++++
 gdb/mi/mi-cmds.c                              |   2 +
 gdb/mi/mi-cmds.h                              |   1 +
 gdb/shadow-stack.c                            | 662 ++++++++++++++++++
 gdb/shadow-stack.h                            | 106 +++
 gdb/stack.c                                   | 382 ++++++----
 gdb/stack.h                                   |  55 ++
 .../amd64-shadow-stack-backtrace-signal.exp   |  54 ++
 .../gdb.arch/amd64-shadow-stack-cmds.exp      |  88 +++
 .../gdb.arch/amd64-shadow-stack-signal.c      |  31 +
 gdb/testsuite/gdb.base/help.exp               |   6 +-
 gdb/testsuite/gdb.base/options.exp            |   7 +-
 .../gdb.mi/mi-shadow-stack-signal.exp         |  69 ++
 gdb/testsuite/gdb.mi/mi-shadow-stack.exp      |  65 ++
 29 files changed, 2105 insertions(+), 435 deletions(-)
 create mode 100644 gdb/shadow-stack.c
 create mode 100644 gdb/shadow-stack.h
 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
 create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
 create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack.exp

-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer.
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-10-31  1:31   ` Thiago Jung Bauermann
  2025-09-23 11:18 ` [PATCH 2/9] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

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 commit a subcommmand "backtrace shadow" of the ordinary
backtrace command will be added to print the shadow stack backtrace.
This requires more target-independent logic to handle the shadow stack
pointer.  To avoid that we duplicate the logic, add new source and header
files "shadow-stack" for the implementation of shadow_stack_push and shadow
stack pointer unwinding in a target-independent way.
---
 gdb/Makefile.in           |   2 +
 gdb/aarch64-linux-tdep.c  |  51 +-----------
 gdb/aarch64-tdep.c        |  42 ++--------
 gdb/amd64-linux-tdep.c    | 129 -----------------------------
 gdb/amd64-tdep.c          |  20 +++++
 gdb/gdbarch-gen.c         | 126 ++++++++++++++++++++++------
 gdb/gdbarch-gen.h         |  65 ++++++++++++---
 gdb/gdbarch_components.py |  83 ++++++++++++++++---
 gdb/infcall.c             |   4 +-
 gdb/linux-tdep.c          |   2 +
 gdb/shadow-stack.c        | 167 ++++++++++++++++++++++++++++++++++++++
 gdb/shadow-stack.h        |  39 +++++++++
 12 files changed, 462 insertions(+), 268 deletions(-)
 create mode 100644 gdb/shadow-stack.c
 create mode 100644 gdb/shadow-stack.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index beacefcfdd5..9cb782fe88c 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1191,6 +1191,7 @@ COMMON_SFILES = \
 	sentinel-frame.c \
 	ser-event.c \
 	serial.c \
+	shadow-stack.c \
 	skip.c \
 	solib.c \
 	solib-target.c \
@@ -1653,6 +1654,7 @@ HFILES_NO_SRCDIR = \
 	serial.h \
 	ser-tcp.h \
 	ser-unix.h \
+	shadow-stack.h \
 	sh-tdep.h \
 	sim-regno.h \
 	skip.h \
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index 048be4f3532..2928b723e0b 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -64,6 +64,7 @@
 #include "elf/common.h"
 #include "elf/aarch64.h"
 #include "arch/aarch64-insn.h"
+#include "shadow-stack.h"
 
 /* For std::pow */
 #include <cmath>
@@ -2562,54 +2563,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.  */
 
@@ -3188,7 +3141,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 500ac77d75a..95af82c2632 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -58,6 +58,9 @@
 
 /* For inferior_ptid and current_inferior ().  */
 #include "inferior.h"
+
+#include "shadow-stack.h"
+
 /* For std::sqrt and std::pow.  */
 #include <cmath>
 
@@ -1893,29 +1896,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
@@ -1933,15 +1913,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
@@ -3683,7 +3654,7 @@ aarch64_displaced_step_b (const int is_bl, const int32_t offset,
       gdbarch_get_shadow_stack_pointer (dsd->regs->arch (), dsd->regs,
 					gcs_is_enabled);
       if (gcs_is_enabled)
-	aarch64_push_gcs_entry (dsd->regs, data->insn_addr + 4);
+	shadow_stack_push (dsd->regs->arch (), dsd->regs, data->insn_addr + 4);
     }
 }
 
@@ -3847,7 +3818,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->arch (), dsd->regs, data->insn_addr + 4);
     }
   else
     aarch64_emit_insn (dsd->insn_buf, insn);
@@ -4871,9 +4842,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 a21f8a9a5ce..f0db3b7a1b4 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -48,8 +48,6 @@
 #include "arch/amd64-linux-tdesc.h"
 #include "inferior.h"
 #include "x86-tdep.h"
-#include "dwarf2/frame.h"
-#include "frame-unwind.h"
 
 /* The syscall's XML filename for i386.  */
 #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
@@ -1923,18 +1921,6 @@ amd64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid,
   return dtv_addr;
 }
 
-/* Return the number of bytes required to update the shadow stack pointer
-   by one element.  For x32 the shadow stack elements are still 64-bit
-   aligned.  Thus, gdbarch_addr_bit cannot be used to compute the new
-   stack pointer.  */
-
-static inline int
-amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
-{
-  const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
-  return (binfo->bits_per_word / binfo->bits_per_byte);
-}
-
 /* Read the shadow stack pointer register and return its value, if
    possible.  */
 
@@ -1966,119 +1952,6 @@ 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.  */
-
-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)
-{
-  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;
-    }
-}
-
 static void
 amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
 			     int num_disp_step_buffers)
@@ -2137,10 +2010,8 @@ 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);
 }
 
 static void
diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c
index 9245889210b..5406275e199 100755
--- a/gdb/amd64-tdep.c
+++ b/gdb/amd64-tdep.c
@@ -51,6 +51,8 @@
 #include "x86-tdep.h"
 #include "amd64-ravenscar-thread.h"
 #include "gdbsupport/selftest.h"
+#include "shadow-stack.h"
+#include "dwarf2/frame.h"
 
 /* Note that the AMD64 architecture was previously known as x86-64.
    The latter is (forever) engraved into the canonical system name as
@@ -3493,6 +3495,21 @@ amd64_in_indirect_branch_thunk (struct gdbarch *gdbarch, CORE_ADDR pc)
 				       AMD64_RIP_REGNUM);
 }
 
+static void
+amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg *reg,
+		const frame_info_ptr &this_frame)
+{
+  if (regnum == gdbarch_pc_regnum (gdbarch))
+    reg->how = DWARF2_FRAME_REG_RA;
+  else if (regnum == gdbarch_sp_regnum (gdbarch))
+    reg->how = DWARF2_FRAME_REG_CFA;
+  else if (regnum == AMD64_PL3_SSP_REGNUM)
+    {
+      reg->how = DWARF2_FRAME_REG_FN;
+      reg->loc.fn = dwarf2_prev_ssp;
+    }
+}
+
 void
 amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
 		const target_desc *default_tdesc)
@@ -3660,6 +3677,9 @@ amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
   set_gdbarch_in_indirect_branch_thunk (gdbarch,
 					amd64_in_indirect_branch_thunk);
 
+  set_gdbarch_ssp_regnum (gdbarch, tdep->ssp_regnum);
+  dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
+
   register_amd64_ravenscar_ops (gdbarch);
 }
 
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index 7887b1386c6..640576f4b85 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_stab_reg_to_regnum_ftype *stab_reg_to_regnum = no_op_reg_to_regnum;
   gdbarch_ecoff_reg_to_regnum_ftype *ecoff_reg_to_regnum = no_op_reg_to_regnum;
   gdbarch_sdb_reg_to_regnum_ftype *sdb_reg_to_regnum = no_op_reg_to_regnum;
@@ -261,8 +262,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_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr;
+  gdbarch_address_in_shadow_stack_memory_range_ftype *address_in_shadow_stack_memory_range = nullptr;
+  int shadow_stack_element_size_aligned = 8;
 };
 
 /* Create a new ``struct gdbarch'' based on information provided by
@@ -348,6 +351,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 stab_reg_to_regnum, invalid_p == 0.  */
   /* Skip verify of ecoff_reg_to_regnum, invalid_p == 0.  */
   /* Skip verify of sdb_reg_to_regnum, invalid_p == 0.  */
@@ -535,8 +539,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 top_addr_empty_shadow_stack, has predicate.  */
+  /* Skip verify of address_in_shadow_stack_memory_range, 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 ());
@@ -709,6 +715,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: stab_reg_to_regnum = <%s>\n",
 	      host_address_to_string (gdbarch->stab_reg_to_regnum));
@@ -1405,15 +1414,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_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: 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: shadow_stack_element_size_aligned = %s\n",
+	      plongest (gdbarch->shadow_stack_element_size_aligned));
   if (gdbarch->dump_tdep != NULL)
     gdbarch->dump_tdep (gdbarch, file);
 }
@@ -2161,6 +2179,23 @@ set_gdbarch_fp0_regnum (struct gdbarch *gdbarch,
   gdbarch->fp0_regnum = fp0_regnum;
 }
 
+int
+gdbarch_ssp_regnum (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  /* Skip verify of ssp_regnum, invalid_p == 0.  */
+  if (gdbarch_debug >= 2)
+    gdb_printf (gdb_stdlog, "gdbarch_ssp_regnum called\n");
+  return gdbarch->ssp_regnum;
+}
+
+void
+set_gdbarch_ssp_regnum (struct gdbarch *gdbarch,
+			int ssp_regnum)
+{
+  gdbarch->ssp_regnum = ssp_regnum;
+}
+
 int
 gdbarch_stab_reg_to_regnum (struct gdbarch *gdbarch, int stab_regnr)
 {
@@ -5543,43 +5578,84 @@ set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch,
   gdbarch->core_parse_exec_context = core_parse_exec_context;
 }
 
+std::optional<CORE_ADDR>
+gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->get_shadow_stack_pointer != NULL);
+  if (gdbarch_debug >= 2)
+    gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer called\n");
+  return gdbarch->get_shadow_stack_pointer (gdbarch, regcache, shadow_stack_enabled);
+}
+
+void
+set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch,
+				      gdbarch_get_shadow_stack_pointer_ftype get_shadow_stack_pointer)
+{
+  gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer;
+}
+
 bool
-gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch)
+gdbarch_top_addr_empty_shadow_stack_p (struct gdbarch *gdbarch)
 {
   gdb_assert (gdbarch != NULL);
-  return gdbarch->shadow_stack_push != NULL;
+  return gdbarch->top_addr_empty_shadow_stack != NULL;
+}
+
+bool
+gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, const CORE_ADDR ADDR, const std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->top_addr_empty_shadow_stack != NULL);
+  if (gdbarch_debug >= 2)
+    gdb_printf (gdb_stdlog, "gdbarch_top_addr_empty_shadow_stack called\n");
+  return gdbarch->top_addr_empty_shadow_stack (gdbarch, ADDR, range);
 }
 
 void
-gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache)
+set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch,
+					 gdbarch_top_addr_empty_shadow_stack_ftype top_addr_empty_shadow_stack)
+{
+  gdbarch->top_addr_empty_shadow_stack = top_addr_empty_shadow_stack;
+}
+
+bool
+gdbarch_address_in_shadow_stack_memory_range_p (struct gdbarch *gdbarch)
 {
   gdb_assert (gdbarch != NULL);
-  gdb_assert (gdbarch->shadow_stack_push != NULL);
+  return gdbarch->address_in_shadow_stack_memory_range != NULL;
+}
+
+bool
+gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, CORE_ADDR ssp, std::pair<CORE_ADDR, CORE_ADDR> *range)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->address_in_shadow_stack_memory_range != NULL);
   if (gdbarch_debug >= 2)
-    gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_push called\n");
-  gdbarch->shadow_stack_push (gdbarch, new_addr, regcache);
+    gdb_printf (gdb_stdlog, "gdbarch_address_in_shadow_stack_memory_range called\n");
+  return gdbarch->address_in_shadow_stack_memory_range (ssp, range);
 }
 
 void
-set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
-			       gdbarch_shadow_stack_push_ftype shadow_stack_push)
+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->shadow_stack_push = shadow_stack_push;
+  gdbarch->address_in_shadow_stack_memory_range = address_in_shadow_stack_memory_range;
 }
 
-std::optional<CORE_ADDR>
-gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled)
+int
+gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch)
 {
   gdb_assert (gdbarch != NULL);
-  gdb_assert (gdbarch->get_shadow_stack_pointer != NULL);
+  /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0.  */
   if (gdbarch_debug >= 2)
-    gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer called\n");
-  return gdbarch->get_shadow_stack_pointer (gdbarch, regcache, shadow_stack_enabled);
+    gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_element_size_aligned called\n");
+  return gdbarch->shadow_stack_element_size_aligned;
 }
 
 void
-set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch,
-				      gdbarch_get_shadow_stack_pointer_ftype get_shadow_stack_pointer)
+set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch,
+					       int shadow_stack_element_size_aligned)
 {
-  gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer;
+  gdbarch->shadow_stack_element_size_aligned = shadow_stack_element_size_aligned;
 }
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index 24fa8bab2db..34a32c346e8 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -283,6 +283,12 @@ extern void set_gdbarch_ps_regnum (struct gdbarch *gdbarch, int ps_regnum);
 extern int gdbarch_fp0_regnum (struct gdbarch *gdbarch);
 extern void set_gdbarch_fp0_regnum (struct gdbarch *gdbarch, int fp0_regnum);
 
+/* Register number for the shadow stack pointer.  For inferior calls, the
+   gdbarch value ssp_regnum has to be provided. */
+
+extern int gdbarch_ssp_regnum (struct gdbarch *gdbarch);
+extern void set_gdbarch_ssp_regnum (struct gdbarch *gdbarch, int ssp_regnum);
+
 /* Convert stab register number (from `r' declaration) to a gdb REGNUM. */
 
 typedef int (gdbarch_stab_reg_to_regnum_ftype) (struct gdbarch *gdbarch, int stab_regnr);
@@ -1793,19 +1799,21 @@ extern void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarc
 /* Some targets support special hardware-assisted control-flow protection
    technologies.  For example, the Intel Control-Flow Enforcement Technology
    (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
-   To enable shadow stack support for inferior calls the shadow_stack_push
-   gdbarch hook has to be provided.  The get_shadow_stack_pointer gdbarch
-   hook has to be provided to enable displaced stepping.
-
-   Push NEW_ADDR to the shadow stack and update the shadow stack pointer. */
-
-extern bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch);
-
-typedef void (gdbarch_shadow_stack_push_ftype) (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
-extern void gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
-extern void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shadow_stack_push_ftype *shadow_stack_push);
-
-/* If possible, return the shadow stack pointer.  If the shadow stack
+   For GDB shadow stack support the following methods or values must be
+   provided:
+   - get_shadow_stack_pointer: required for displaced stepping
+   - address_in_shadow_stack_memory_range and top_addr_empty_shadow_stack:
+     required for shadow stack pointer unwinding
+   - address_in_shadow_stack_memory_range and the value 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.
+   In case the shadow stack can be empty even though the shadow stack pointer
+   points to shadow stack memory range, implement the gdbarch hook
+   top_addr_empty_shadow_stack.
+
+   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.
@@ -1817,3 +1825,34 @@ extern void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shad
 typedef std::optional<CORE_ADDR> (gdbarch_get_shadow_stack_pointer_ftype) (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
 extern std::optional<CORE_ADDR> gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
 extern void set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer);
+
+/* 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
+   inferior calls for shadow stack enabled programs and to enable unwinding
+   of the shadow stack pointer. */
+
+extern bool gdbarch_top_addr_empty_shadow_stack_p (struct gdbarch *gdbarch);
+
+typedef bool (gdbarch_top_addr_empty_shadow_stack_ftype) (struct gdbarch *gdbarch, const CORE_ADDR ADDR, const std::pair<CORE_ADDR, CORE_ADDR> range);
+extern bool gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, const CORE_ADDR ADDR, const std::pair<CORE_ADDR, CORE_ADDR> range);
+extern void set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack);
+
+/* Returns true if ADDR belongs to a shadow stack memory range.  If this is
+   the case, assign the shadow stack memory range to RANGE
+   [start_address, end_address).  This hook has to be provided in case the
+   shadow stack can be empty even though SSP points to shadow stack memory
+   defined by RANGE [start_address, end_address).  This is possible for the
+   Guarded Control Stack on ARM. */
+
+extern bool gdbarch_address_in_shadow_stack_memory_range_p (struct gdbarch *gdbarch);
+
+typedef bool (gdbarch_address_in_shadow_stack_memory_range_ftype) (CORE_ADDR ssp, std::pair<CORE_ADDR, CORE_ADDR> *range);
+extern bool gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, CORE_ADDR ssp, std::pair<CORE_ADDR, CORE_ADDR> *range);
+extern void set_gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, gdbarch_address_in_shadow_stack_memory_range_ftype *address_in_shadow_stack_memory_range);
+
+/* The number of bytes required to update the shadow stack pointer by one
+   element.  In case the alignment is not the predefault (8 bytes), configure
+   this value. */
+
+extern int gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch);
+extern void set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch, int shadow_stack_element_size_aligned);
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index 3820ae3c94c..5223553941f 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -544,6 +544,17 @@ Value(
     invalid=False,
 )
 
+Value(
+    comment="""
+Register number for the shadow stack pointer.  For inferior calls, the
+gdbarch value ssp_regnum has to be provided.
+""",
+    type="int",
+    name="ssp_regnum",
+    predefault="-1",
+    invalid=False,
+)
+
 Method(
     comment="""
 Convert stab register number (from `r' declaration) to a gdb REGNUM.
@@ -2852,20 +2863,20 @@ Method(
 Some targets support special hardware-assisted control-flow protection
 technologies.  For example, the Intel Control-Flow Enforcement Technology
 (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
-To enable shadow stack support for inferior calls the shadow_stack_push
-gdbarch hook has to be provided.  The get_shadow_stack_pointer gdbarch
-hook has to be provided to enable displaced stepping.
-
-Push NEW_ADDR to the shadow stack and update the shadow stack pointer.
-""",
-    type="void",
-    name="shadow_stack_push",
-    params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")],
-    predicate=True,
-)
+For GDB shadow stack support the following methods or values must be
+provided:
+- get_shadow_stack_pointer: required for displaced stepping
+- address_in_shadow_stack_memory_range and top_addr_empty_shadow_stack:
+  required for shadow stack pointer unwinding
+- address_in_shadow_stack_memory_range and the value 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.
+In case the shadow stack can be empty even though the shadow stack pointer
+points to shadow stack memory range, implement the gdbarch hook
+top_addr_empty_shadow_stack.
 
-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
@@ -2881,3 +2892,49 @@ SHADOW_STACK_ENABLED to false.
     predefault="default_get_shadow_stack_pointer",
     invalid=False,
 )
+
+Method(
+    comment="""
+Return true if ADDR points to the top of an empty shadow stack, defined by
+RANGE [start_address, end_address).  This hook has to be provided to enable
+inferior calls for shadow stack enabled programs and 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,
+)
+
+Function(
+    comment="""
+Returns true if ADDR belongs to a shadow stack memory range.  If this is
+the case, assign the shadow stack memory range to RANGE
+[start_address, end_address).  This hook has to be provided in case the
+shadow stack can be empty even though SSP points to shadow stack memory
+defined by RANGE [start_address, end_address).  This is possible for the
+Guarded Control Stack on ARM.
+""",
+    type="bool",
+    name="address_in_shadow_stack_memory_range",
+    params=[
+        ("CORE_ADDR", "ssp"),
+        ("std::pair<CORE_ADDR, CORE_ADDR> *", "range")
+    ],
+    predicate=True,
+)
+
+Value(
+    comment="""
+The number of bytes required to update the shadow stack pointer by one
+element.  In case the alignment is not the predefault (8 bytes), configure
+this value.
+""",
+    type="int",
+    name="shadow_stack_element_size_aligned",
+    predefault="8",
+    invalid=False,
+)
diff --git a/gdb/infcall.c b/gdb/infcall.c
index c4b4c8f0bea..46f9fd9342e 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -42,6 +42,7 @@
 #include "thread-fsm.h"
 #include <algorithm>
 #include "gdbsupport/scope-exit.h"
+#include "shadow-stack.h"
 #include <list>
 
 /* True if we are debugging inferior calls.  */
@@ -1462,8 +1463,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 (gdbarch, 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 4b679c8759e..1c1ebd5a99f 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -3118,6 +3118,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..d153d5fc846
--- /dev/null
+++ b/gdb/shadow-stack.c
@@ -0,0 +1,167 @@
+/* Manage a shadow stack pointer for GDB, the GNU debugger.
+
+   Copyright (C) 2024-2025 Free Software Foundation, Inc.
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "extract-store-integer.h"
+#include "frame.h"
+#include "frame-unwind.h"
+#include "shadow-stack.h"
+
+enum class ssp_update_direction
+{
+  /* Update ssp towards the bottom of the shadow stack.  */
+  bottom = 0,
+
+  /* Update ssp towards the top of the shadow stack.  */
+  top
+};
+
+/* Return a new shadow stack pointer which is incremented or decremented
+   by COUNT elements 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::bottom)
+		      : (direction == ssp_update_direction::top);
+
+  CORE_ADDR new_ssp;
+  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 (gdbarch *gdbarch, regcache *regcache,
+			const CORE_ADDR new_addr)
+{
+  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::top);
+
+  /* If NEW_SSP does not point to shadow stack memory, we assume the stack
+     is full.  */
+  std::pair<CORE_ADDR, CORE_ADDR> range;
+  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch,
+						     new_ssp,
+						     &range))
+    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 int element_size
+    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
+
+  const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+
+  write_memory_unsigned_integer (new_ssp, element_size, byte_order,
+				 (ULONGEST) new_addr);
+
+  regcache_raw_write_unsigned (regcache,
+			       gdbarch_ssp_regnum (gdbarch),
+			       new_ssp);
+}
+
+/* See shadow-stack.h.  */
+
+value *
+dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache,
+		 int regnum)
+{
+  value *v = frame_unwind_got_register (this_frame, regnum, regnum);
+  gdb_assert (v != nullptr);
+
+  gdbarch *gdbarch = get_frame_arch (this_frame);
+
+  if (gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
+      && v->entirely_available () && !v->optimized_out ())
+    {
+      const int size = register_size (gdbarch, regnum);
+      bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+      CORE_ADDR ssp = extract_unsigned_integer
+	(v->contents_all ().data (), size, byte_order);
+
+      /* Only if the current shadow stack pointer SSP points to shadow
+	 stack memory a valid previous shadow stack pointer can be
+	 calculated.  */
+      std::pair<CORE_ADDR, CORE_ADDR> range;
+      if (gdbarch_address_in_shadow_stack_memory_range (gdbarch, ssp,
+							&range))
+	{
+	  /* Note that a shadow stack memory range can change, due to
+	     shadow stack switches for instance on x86 for an inter-
+	     privilege far call or when calling an interrupt/exception
+	     handler at a higher privilege level.  Shadow stack for
+	     userspace is supported for amd64 linux starting with
+	     Linux kernel v6.6.  However, shadow stack switches are not
+	     supported due to missing kernel space support.  We therefore
+	     implement this unwinder without support for shadow stack
+	     switches for now.  */
+	  const CORE_ADDR new_ssp
+	    = update_shadow_stack_pointer (gdbarch, ssp,
+					   ssp_update_direction::bottom);
+
+	  /* On x86, if NEW_SSP points to the end of RANGE, it indicates
+	     that NEW_SSP is valid, but the shadow stack is empty.  In
+	     contrast, on ARM's Guarded Control Stack, if NEW_SSP points
+	     to the end of RANGE, it means that the shadow stack pointer
+	     is invalid.  */
+	  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);
+
+	  /* Check whether the new SSP is valid.  Depending on the
+	     architecture, this may rely on both
+	     IS_TOP_ADDR_EMPTY_SHADOW_STACK and the return value of
+	     gdbarch_address_in_shadow_stack_memory_range, or on the
+	     latter only.  */
+	  if (is_top_addr_empty_shadow_stack
+	      || gdbarch_address_in_shadow_stack_memory_range (gdbarch,
+							       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..5c3ba80974e
--- /dev/null
+++ b/gdb/shadow-stack.h
@@ -0,0 +1,39 @@
+/* Definitions to manage a shadow stack pointer for GDB, the GNU debugger.
+
+   Copyright (C) 2024-2025 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDB_SHADOW_STACK_H
+#define GDB_SHADOW_STACK_H
+
+/* If shadow stack is enabled, push the address NEW_ADDR on the shadow
+   stack and update the shadow stack pointer accordingly.  */
+
+void shadow_stack_push (gdbarch *gdbarch, 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: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 2/9] gdb: Refactor 'stack.c:print_frame'.
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
  2025-09-23 11:18 ` [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-10-03 20:05   ` Tom Tromey
  2025-09-23 11:18 ` [PATCH 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

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."
---
 gdb/stack.c | 90 +++++++++++++++++++++++++++++++++--------------------
 1 file changed, 57 insertions(+), 33 deletions(-)

diff --git a/gdb/stack.c b/gdb/stack.c
index add1c547b55..3efb4ebdbc2 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1314,6 +1314,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,
@@ -1359,12 +1413,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 (" (");
@@ -1397,28 +1447,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))
@@ -1428,12 +1457,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: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument.
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
  2025-09-23 11:18 ` [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
  2025-09-23 11:18 ` [PATCH 2/9] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-10-03 19:56   ` Tom Tromey
  2025-09-23 11:18 ` [PATCH 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

The function will be used in a later patch "gdb: Implement 'bt shadow'
to print the shadow stack backtrace.".
---
 gdb/stack.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/gdb/stack.c b/gdb/stack.c
index 3efb4ebdbc2..1c84b17bc14 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -977,15 +977,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 (" [");
@@ -994,6 +993,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: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c.
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (2 preceding siblings ...)
  2025-09-23 11:18 ` [PATCH 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-10-03 19:55   ` Tom Tromey
  2025-09-23 11:18 ` [PATCH 5/9] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

To avoid code duplication, create a new function 'stack.c:find_symbol_funname',
which will be used in 'stack.c:find_frame_funname' and
'stack.c:info_frame_command_core'.
The function will also be used in a following commit.
---
 gdb/stack.c | 72 ++++++++++++++++++++++++-----------------------------
 1 file changed, 32 insertions(+), 40 deletions(-)

diff --git a/gdb/stack.c b/gdb/stack.c
index 1c84b17bc14..91ae4046ad6 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1267,6 +1267,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.reset (xstrdup (print_name));
+    }
+
+  return funname;
+}
+
 
 /* Attempt to obtain the name, FUNLANG and optionally FUNCP of the function
    corresponding to FRAME.  */
@@ -1285,25 +1312,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.reset (xstrdup (print_name));
+      funname = find_symbol_funname (func);
     }
   else
     {
@@ -1509,8 +1521,7 @@ info_frame_command_core (const frame_info_ptr &fi, bool selected_frame_p)
   struct symtab *s;
   frame_info_ptr calling_frame_info;
   int numregs;
-  const char *funname = 0;
-  enum language funlang = language_unknown;
+  gdb::unique_xmalloc_ptr<char> funname;
   const char *pc_regname;
   struct gdbarch *gdbarch;
   std::optional<CORE_ADDR> frame_pc;
@@ -1538,32 +1549,13 @@ info_frame_command_core (const frame_info_ptr &fi, bool selected_frame_p)
   func = get_frame_function (fi);
   symtab_and_line sal = find_frame_sal (fi);
   s = sal.symtab;
-  gdb::unique_xmalloc_ptr<char> func_only;
   if (func)
-    {
-      funname = func->print_name ();
-      funlang = func->language ();
-      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.  */
-	  func_only = cp_remove_params (funname);
-
-	  if (func_only)
-	    funname = func_only.get ();
-	}
-    }
+    funname = find_symbol_funname (func);
   else if (frame_pc.has_value ())
     {
       bound_minimal_symbol msymbol = lookup_minimal_symbol_by_pc (*frame_pc);
       if (msymbol.minsym != NULL)
-	{
-	  funname = msymbol.minsym->print_name ();
-	  funlang = msymbol.minsym->language ();
-	}
+	funname.reset (xstrdup (msymbol.minsym->print_name ()));
     }
   calling_frame_info = get_prev_frame (fi);
 
@@ -1588,7 +1580,7 @@ info_frame_command_core (const frame_info_ptr &fi, bool selected_frame_p)
   if (funname)
     {
       gdb_printf (" in ");
-      gdb_puts (funname);
+      gdb_puts (funname.get ());
     }
   gdb_stdout->wrap_here (3);
   if (sal.symtab)
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 5/9] gdb: Refactor 'stack.c:print_frame_info'.
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (3 preceding siblings ...)
  2025-09-23 11:18 ` [PATCH 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-10-03 20:03   ` Tom Tromey
  2025-09-23 11:18 ` [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace Christina Schimpe
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

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."
---
 gdb/stack.c | 159 +++++++++++++++++++++++++++++++---------------------
 1 file changed, 94 insertions(+), 65 deletions(-)

diff --git a/gdb/stack.c b/gdb/stack.c
index 91ae4046ad6..40756f74a00 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1013,6 +1013,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.
@@ -1029,8 +1109,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)
@@ -1101,75 +1179,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: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (4 preceding siblings ...)
  2025-09-23 11:18 ` [PATCH 5/9] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-09-23 11:47   ` Eli Zaretskii
                     ` (2 more replies)
  2025-09-23 11:18 ` [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
                   ` (3 subsequent siblings)
  9 siblings, 3 replies; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

Add a subcommand 'bt shadow' for the ordinary backtrace command which
prints the shadow stack backtrace.
Similar to the ordinary backtrace command 'bt shadow' can be configured
using COUNT and the command line option -frame-info.  However, we always
print the address and the command is not affected by the setting
"print address" as well as the setting "print frame-info location-and-address".
Also we do not print the frame arguments.

Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]

Help output:
~~
(gdb) help bt shadow
Print backtrace of all shadow stack frames, or innermost COUNT frames.
Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]

Options:
  -frame-info auto|source-line|location|source-and-location|location-and-address|short-location
    Set printing of shadow stack frame information.

With a negative COUNT, print outermost -COUNT elements.
~~

Example for the output of 'bt shadow' on amd64 linux:
~~
(gdb) bt shadow
/#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
/#1  0x000000000040112f in main at amd64-shadow-stack.c:21
/#2  0x00007ffff7c3fe70 in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
/#3  0x00007ffff7c3ff20 in __libc_start_main_impl at ../csu/libc-start.c:128
/#4  0x0000000000401045 in _start
~~

This commit also adds a test for 'bt shadow' on amd64.  Note that
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                                      |   7 +
 gdb/annotate.c                                |  93 +++-
 gdb/annotate.h                                |  18 +-
 gdb/doc/gdb.texinfo                           |  48 ++
 gdb/shadow-stack.c                            | 504 +++++++++++++++++-
 gdb/shadow-stack.h                            |  19 +
 gdb/stack.c                                   | 104 ++--
 gdb/stack.h                                   |  55 ++
 .../gdb.arch/amd64-shadow-stack-cmds.exp      |  88 +++
 gdb/testsuite/gdb.base/help.exp               |   6 +-
 gdb/testsuite/gdb.base/options.exp            |   7 +-
 11 files changed, 885 insertions(+), 64 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 8be367d2424..eb96ff2ae08 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -33,6 +33,13 @@ single-inf-arg in qSupported
   reply with the single-inf-arg feature to indicate that it is able to
   accept arguments as a single string.
 
+* New commands
+
+backtrace shadow [option]... [count | -count]
+bt shadow [option]... [count | -count]
+  Print backtrace of all shadow stack frames, or innermost 'count' frames.
+  The command is a subcommand of the ordinary backtrace command.
+
 *** Changes in GDB 17
 
 * Debugging Linux programs that use x86-64 or x86-64 with 32-bit pointer
diff --git a/gdb/annotate.c b/gdb/annotate.c
index 55db2e2e8e1..5acae566095 100644
--- a/gdb/annotate.c
+++ b/gdb/annotate.c
@@ -506,10 +506,15 @@ annotate_frame_address_end (void)
 }
 
 void
-annotate_frame_function_name (void)
+annotate_frame_function_name (bool shadowstack_frame)
 {
   if (annotation_level == 2)
-    printf_unfiltered (("\n\032\032frame-function-name\n"));
+    {
+      if (!shadowstack_frame)
+	printf_unfiltered (("\n\032\032frame-function-name\n"));
+      else
+	printf_unfiltered (("\n\032\032shadow-stack-frame-function-name\n"));
+    }
 }
 
 void
@@ -520,45 +525,75 @@ annotate_frame_args (void)
 }
 
 void
-annotate_frame_source_begin (void)
+annotate_frame_source_begin (bool shadowstack_frame)
 {
   if (annotation_level == 2)
-    printf_unfiltered (("\n\032\032frame-source-begin\n"));
+    {
+      if (!shadowstack_frame)
+	printf_unfiltered (("\n\032\032frame-source-begin\n"));
+      else
+	printf_unfiltered (("\n\032\032shadow-stack-frame-source-begin\n"));
+    }
 }
 
 void
-annotate_frame_source_file (void)
+annotate_frame_source_file (bool shadowstack_frame)
 {
   if (annotation_level == 2)
-    printf_unfiltered (("\n\032\032frame-source-file\n"));
+    {
+      if (!shadowstack_frame)
+	printf_unfiltered (("\n\032\032frame-source-file\n"));
+      else
+	printf_unfiltered (("\n\032\032shadow-stack-frame-source-file\n"));
+    }
 }
 
 void
-annotate_frame_source_file_end (void)
+annotate_frame_source_file_end (bool shadowstack_frame)
 {
   if (annotation_level == 2)
-    printf_unfiltered (("\n\032\032frame-source-file-end\n"));
+    {
+      if (!shadowstack_frame)
+	printf_unfiltered (("\n\032\032frame-source-file-end\n"));
+      else
+	printf_unfiltered (("\n\032\032shadow-stack-frame-source-file-end\n"));
+    }
 }
 
 void
-annotate_frame_source_line (void)
+annotate_frame_source_line (bool shadowstack_frame)
 {
   if (annotation_level == 2)
-    printf_unfiltered (("\n\032\032frame-source-line\n"));
+    {
+      if (!shadowstack_frame)
+	printf_unfiltered (("\n\032\032frame-source-line\n"));
+      else
+	printf_unfiltered (("\n\032\032shadow-stack-frame-source-line\n"));
+    }
 }
 
 void
-annotate_frame_source_end (void)
+annotate_frame_source_end (bool shadowstack_frame)
 {
   if (annotation_level == 2)
-    printf_unfiltered (("\n\032\032frame-source-end\n"));
+    {
+      if (!shadowstack_frame)
+	printf_unfiltered (("\n\032\032frame-source-end\n"));
+      else
+	printf_unfiltered (("\n\032\032shadow-stack-frame-source-end\n"));
+    }
 }
 
 void
-annotate_frame_where (void)
+annotate_frame_where (bool shadowstack_frame)
 {
   if (annotation_level == 2)
-    printf_unfiltered (("\n\032\032frame-where\n"));
+    {
+      if (!shadowstack_frame)
+	printf_unfiltered (("\n\032\032frame-where\n"));
+      else
+	printf_unfiltered (("\n\032\032shadow-stack-frame-where\n"));
+    }
 }
 
 void
@@ -568,6 +603,36 @@ annotate_frame_end (void)
     printf_unfiltered (("\n\032\032frame-end\n"));
 }
 \f
+
+/* Annotations for shadow stack frames.  */
+
+void
+annotate_shadowstack_frame_begin (int level, gdbarch *gdbarch, CORE_ADDR pc)
+{
+  if (annotation_level > 1)
+    printf_unfiltered (("\n\032\032shadow-stack-frame-begin %d %s\n"),
+		       level, paddress (gdbarch, pc));
+}
+
+void annotate_shadowstack_frame_address (void)
+{
+  if (annotation_level == 2)
+    printf_unfiltered (("\n\032\032shadow-stack-frame-address\n"));
+}
+
+void
+annotate_shadowstack_frame_address_end (void)
+{
+  if (annotation_level == 2)
+    printf_unfiltered (("\n\032\032shadow-stack-frame-address-end\n"));
+}
+
+void annotate_shadowstack_frame_end ()
+{
+  if (annotation_level == 2)
+    printf_unfiltered (("\n\032\032shadow-stack-frame-end\n"));
+}
+
 void
 annotate_array_section_begin (int idx, struct type *elttype)
 {
diff --git a/gdb/annotate.h b/gdb/annotate.h
index 0f394f5ccdc..675b0b771a0 100644
--- a/gdb/annotate.h
+++ b/gdb/annotate.h
@@ -121,15 +121,19 @@ extern void annotate_function_call (void);
 extern void annotate_signal_handler_caller (void);
 extern void annotate_frame_address (void);
 extern void annotate_frame_address_end (void);
-extern void annotate_frame_function_name (void);
+extern void annotate_frame_function_name (bool shadowstack_frame = false);
 extern void annotate_frame_args (void);
-extern void annotate_frame_source_begin (void);
-extern void annotate_frame_source_file (void);
-extern void annotate_frame_source_file_end (void);
-extern void annotate_frame_source_line (void);
-extern void annotate_frame_source_end (void);
-extern void annotate_frame_where (void);
+extern void annotate_frame_source_begin (bool shadowstack_frame = false);
+extern void annotate_frame_source_file (bool shadowstack_frame = false);
+extern void annotate_frame_source_file_end (bool shadowstack_frame = false);
+extern void annotate_frame_source_line (bool shadowstack_frame = false);
+extern void annotate_frame_source_end (bool shadowstack_frame = false);
+extern void annotate_frame_where (bool shadowstack_frame = false);
 extern void annotate_frame_end (void);
+extern void annotate_shadowstack_frame_begin (int, gdbarch *, CORE_ADDR);
+extern void annotate_shadowstack_frame_address (void);
+extern void annotate_shadowstack_frame_address_end (void);
+extern void annotate_shadowstack_frame_end (void);
 
 extern void annotate_array_section_begin (int, struct type *);
 extern void annotate_elt_rep (unsigned int);
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index e8515883820..ebda4546b58 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8840,6 +8840,53 @@ Display an absolute filename.
 Show the current way to display filenames.
 @end table
 
+@subsection Shadow stack backtrace
+@value{GDBN} provides a subcommand of the backtrace command to print the
+shadow stack backtrace.
+
+@anchor{shadowstack-backtrace-command}
+@kindex backtrace shadow
+@kindex bt shadow @r{(@code{backtrace shadow})}
+To print a backtrace of the entire shadow stack, use the
+@code{backtrace shadow} command, or its alias @code{bt shadow}.  This
+command will print 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 (@xref{CET}) on x86.
+
+@table @code
+@item backtrace shadow [@var{option}]@dots{} @dots{} [@var{count}]
+@itemx bt shadow [@var{option}]@dots{} @dots{} [@var{count}]
+Print the backtrace of all shadow stack frames
+@end table
+
+In contrast to the backtrace command,
+
+@itemize @bullet
+@item the options @samp{-past-main} or @samp{-past-entry} are not supported
+and all available shadow stack frames are printed by default
+@item Python frame filters are not supported
+@item frame local variables are not printed
+@item frame arguments are not printed and therefore the options
+@samp{-frame-arguments} and @samp{-raw-frame-arguments} are not supported.
+@item configuring the option @samp{-frame-info} or its corresponding
+global setting @ref{set print frame-info} to @code{location} is treated
+same as configuring it to @code{location-and-address}, as @value{GDBN}
+always prints the address for shadow stack frames.
+@end itemize
+
+This is how a shadow stack backtrace looks like on amd64:
+@smallexample
+@group
+#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
+#1  0x000000000040112f in main at amd64-shadow-stack.c:21
+#2  0x00007ffff7c3fe70 in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
+#3  0x00007ffff7c3ff20 in __libc_start_main_impl at ../csu/libc-start.c:128
+#4  0x0000000000401045 in _start
+@end group
+@end smallexample
+
 @node Selection
 @section Selecting a Frame
 
@@ -27132,6 +27179,7 @@ registers
 
 @end itemize
 
+@node CET
 @subsubsection Intel Control-Flow Enforcement Technology.
 @cindex Intel Control-Flow Enforcement Technology.
 
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index d153d5fc846..759ef6989ea 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -23,6 +23,11 @@
 #include "frame.h"
 #include "frame-unwind.h"
 #include "shadow-stack.h"
+#include "annotate.h"
+#include "stack.h"
+#include "solib.h"
+#include "event-top.h"
+#include "cli/cli-style.h"
 
 enum class ssp_update_direction
 {
@@ -38,17 +43,19 @@ enum class ssp_update_direction
 
 static CORE_ADDR
 update_shadow_stack_pointer (gdbarch *gdbarch, CORE_ADDR ssp,
+			     const unsigned int count,
 			     const ssp_update_direction direction)
 {
   bool increment = (gdbarch_stack_grows_down (gdbarch))
 		    ? (direction == ssp_update_direction::bottom)
 		      : (direction == ssp_update_direction::top);
 
-  CORE_ADDR new_ssp;
+  const int element_size
+    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
   if (increment)
-    return ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch);
+    return ssp + count * element_size;
   else
-    return ssp - gdbarch_shadow_stack_element_size_aligned (gdbarch);
+    return ssp - count * element_size;
 }
 
 /* See shadow-stack.h.  */
@@ -68,7 +75,7 @@ void shadow_stack_push (gdbarch *gdbarch, regcache *regcache,
     return;
 
   const CORE_ADDR new_ssp
-    = update_shadow_stack_pointer (gdbarch, *ssp,
+    = update_shadow_stack_pointer (gdbarch, *ssp, 1,
 				   ssp_update_direction::top);
 
   /* If NEW_SSP does not point to shadow stack memory, we assume the stack
@@ -131,7 +138,7 @@ dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache,
 	     implement this unwinder without support for shadow stack
 	     switches for now.  */
 	  const CORE_ADDR new_ssp
-	    = update_shadow_stack_pointer (gdbarch, ssp,
+	    = update_shadow_stack_pointer (gdbarch, ssp, 1,
 					   ssp_update_direction::bottom);
 
 	  /* On x86, if NEW_SSP points to the end of RANGE, it indicates
@@ -165,3 +172,490 @@ dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache,
   return retval;
 
 }
+
+/* Option definitions for some shadow stack related settings.  */
+
+using shadowstack_enum_option_def
+  = gdb::option::enum_option_def<shadow_stack_print_options>;
+
+static const gdb::option::option_def shadowstack_print_option_defs[] = {
+
+  shadowstack_enum_option_def {
+    "frame-info",
+    print_frame_info_choices,
+    [] (shadow_stack_print_options *opt) { return &opt->print_frame_info; },
+    nullptr, /* show_cmd_cb */
+    N_("Set printing of shadow stack frame information."),
+    N_("Show printing of shadow stack frame information."),
+    nullptr /* help_doc */
+  }
+
+};
+
+/* 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_frame_sal).  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;
+}
+
+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
+    (gdbarch *gdbarch, 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;
+  /* 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;
+};
+
+static
+gdb::unique_xmalloc_ptr<char> find_pc_funname (CORE_ADDR pc)
+{
+  symbol *func = find_pc_function (pc);
+  if (func)
+    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.reset (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, gdbarch *gdbarch,
+   const shadow_stack_print_options &print_options,
+   const shadow_stack_frame_info &frame, print_what print_what)
+{
+  if (print_options.print_frame_info != print_frame_info_auto)
+    {
+      /* Use the specific frame information desired by the user.  */
+      print_what
+	= *print_frame_info_to_print_what (print_options.print_frame_info);
+    }
+
+  /* In contrast to find_frame_sal which is used for the ordinary backtrace
+     command, we always want to print the line that is actually referred
+     to by the address in the linetable.  That's why we always pass 0 here
+     as second argument.  */
+  symtab_and_line sal = find_pc_line (frame.value, 0);
+
+  if (should_print_location (print_what) || sal.symtab == nullptr)
+    {
+      gdb::unique_xmalloc_ptr<char> funname
+	= find_pc_funname (frame.value);
+
+      { /* Extra scope to print frame tuple.  */
+	ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame");
+
+	annotate_shadowstack_frame_begin (frame.level, gdbarch,
+					  frame.value);
+
+	uiout->text ("#");
+	uiout->field_fmt_signed (2, ui_left, "level", frame.level);
+
+	annotate_shadowstack_frame_address ();
+
+	/* 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 (gdbarch);
+
+	uiout->field_string
+	  ("addr", hex_string_custom (frame.value, element_size * 2),
+	   address_style.style ());
+
+	annotate_shadowstack_frame_address_end ();
+
+	uiout->text (" in ");
+	print_funname (uiout, funname, true);
+
+	if (print_what != SHORT_LOCATION && sal.symtab != nullptr)
+	  print_filename (uiout, sal, true);
+
+	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, true);
+	  }
+      } /* Extra scope to print frame tuple.  */
+
+      uiout->text ("\n");
+    }
+
+  if (print_what == SRC_LINE || print_what == SRC_AND_LOC)
+    {
+      int 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
+	 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, gdbarch, frame.value, sal, print_address,
+		    mid_statement, "");
+    }
+
+  annotate_shadowstack_frame_end ();
+  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
+  (gdbarch *gdbarch, const shadow_stack_print_options &print_options,
+   const shadow_stack_frame_info &frame, print_what print_what)
+{
+  do_with_buffered_output
+    (do_print_shadow_stack_frame_info, current_uiout, gdbarch,
+     print_options, frame, print_what);
+}
+
+
+/* Extract a char array which can be used for printing a reasonable
+   error message for REASON.  Note that in case REASON has the value
+   NO_ERROR the returned array is empty.  */
+
+static const char *
+ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
+{
+  switch (reason)
+    {
+    case ssp_unwind_stop_reason::no_error:
+      return _("");
+    case ssp_unwind_stop_reason::memory_read_error:
+      return _("shadow stack memory read failure");
+    }
+
+  gdb_assert_not_reached ("invalid reason");
+}
+
+
+/* Read the memory at shadow stack pointer SSP and assign it to
+   RETURN_VALUE.  In case we cannot read the memory, set REASON to
+   ssp_unwind_stop_reason::memory_read_error and return false.  */
+
+static bool
+read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
+			  CORE_ADDR *return_value,
+			  ssp_unwind_stop_reason *reason)
+{
+  /* 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);
+  if (!safe_read_memory_unsigned_integer (ssp, element_size, byte_order,
+					  return_value))
+    {
+      *reason = ssp_unwind_stop_reason::memory_read_error;
+      return false;
+    }
+
+  return true;
+}
+
+/*  If possible, return a shadow stack frame info which is COUNT elements
+    above the bottom of the shadow stack.  FRAME should point to the top
+    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
+  (gdbarch *gdbarch, const std::pair<CORE_ADDR, CORE_ADDR> range,
+   const ULONGEST count, shadow_stack_frame_info &frame)
+{
+  /* Compute the number of bytes on the shadow stack, starting at
+     FRAME->SSP, which depends on the direction the shadow stack
+     grows.  */
+  const int element_size
+    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
+  const unsigned long shadow_stack_bytes
+    = (gdbarch_stack_grows_down (gdbarch))
+       ? range.second - frame.ssp : frame.ssp - range.first + element_size;
+
+  gdb_assert ((shadow_stack_bytes % element_size) == 0);
+  const unsigned long shadow_stack_size
+    = shadow_stack_bytes / element_size;
+  const long level = shadow_stack_size - count;
+
+  /* COUNT exceeds the number of elements on the shadow stack.  Return the
+     starting shadow stack frame info FRAME.  */
+  if (level <= 0)
+    return std::optional<shadow_stack_frame_info> (frame);
+
+  CORE_ADDR new_ssp = update_shadow_stack_pointer
+    (gdbarch, frame.ssp, count, ssp_update_direction::bottom);
+
+  if (gdbarch_stack_grows_down (gdbarch))
+    gdb_assert (new_ssp < range.second);
+  else
+    gdb_assert (new_ssp >= range.first);
+
+  CORE_ADDR new_value;
+  if (!read_shadow_stack_memory (gdbarch, new_ssp, &new_value,
+				 &frame.unwind_stop_reason))
+    return {};
+
+  return std::optional<shadow_stack_frame_info>
+    ({new_ssp, new_value, (ULONGEST) level,
+      ssp_unwind_stop_reason::no_error});
+}
+
+std::optional<shadow_stack_frame_info>
+shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info
+  (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+  /* 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 (gdbarch, this->ssp, 1,
+				   ssp_update_direction::bottom);
+
+  if (gdbarch_stack_grows_down (gdbarch))
+    {
+      /* The shadow stack grows downwards.  */
+      if (new_ssp >= range.second)
+	{
+	  /* We reached the bottom of the shadow stack.  */
+	  return {};
+	}
+      /* We updated new_ssp towards the bottom 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 bottom of the shadow stack.  */
+	  return {};
+	}
+      /* We updated new_ssp towards the bottom of the shadow stack before,
+	 and new_ssp must be pointing to shadow stack memory.  */
+      gdb_assert (new_ssp <= range.second);
+    }
+
+  CORE_ADDR new_value;
+  if (!read_shadow_stack_memory (gdbarch, new_ssp, &new_value,
+				 &this->unwind_stop_reason))
+    return {};
+
+  return std::optional<shadow_stack_frame_info>
+    ({new_ssp, new_value, this->level + 1,
+      ssp_unwind_stop_reason::no_error});
+}
+
+/* Print all elements on the shadow stack or just the innermost COUNT_EXP
+   frames.  */
+
+static void
+backtrace_shadow_command (const shadow_stack_print_options &print_options,
+			  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))
+    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 target."));
+
+  /* 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.  */
+  std::pair<CORE_ADDR, CORE_ADDR> range;
+  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
+						     &range))
+    {
+      /* If the current shadow stack pointer does not point to shadow
+	 stack memory, the shadow stack is empty.  */
+      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;
+  CORE_ADDR new_value;
+  if (read_shadow_stack_memory (gdbarch, *start_ssp, &new_value, &reason))
+    current = {*start_ssp, new_value, 0,
+	       ssp_unwind_stop_reason::no_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
+		       (gdbarch, range, std::abs (count), *current);
+
+	  if (!trailing.has_value ())
+	    reason = current->unwind_stop_reason;
+	}
+    }
+
+  if (!trailing.has_value ())
+    {
+      if (reason > ssp_unwind_stop_reason::no_error)
+	error (_("Cannot print shadow stack backtrace: %s.\n"),
+	       ssp_unwind_stop_reason_to_err_string (reason));
+      else
+	gdb_assert_not_reached ("invalid reason");
+    }
+
+  current = trailing;
+  while (current.has_value () && count--)
+    {
+      QUIT;
+
+      print_shadow_stack_frame_info (gdbarch, print_options, *current,
+				     LOCATION);
+
+      trailing = current;
+      current = current->unwind_prev_shadow_stack_frame_info (gdbarch,
+							      range);
+    }
+
+  /* If we've stopped before the end, mention that.  */
+  if (current && from_tty)
+    gdb_printf (_("(More shadow stack frames follow...)\n"));
+
+  /* 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.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 (gdbarch, trailing->ssp),
+		ssp_unwind_stop_reason_to_err_string
+		  (trailing->unwind_stop_reason));
+}
+
+void
+backtrace_shadow_command (const char *arg, int from_tty)
+{
+  shadow_stack_print_options print_options;
+  print_options.print_frame_info
+    = user_frame_print_options.print_frame_info;
+
+  auto grp = make_backtrace_shadow_options_def_group (&print_options);
+  gdb::option::process_options
+    (&arg, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, grp);
+
+  if (arg != nullptr && *arg == '\0')
+    arg = nullptr;
+
+  backtrace_shadow_command (print_options, arg, from_tty);
+}
+
+std::array<gdb::option::option_def_group, 1>
+make_backtrace_shadow_options_def_group
+  (shadow_stack_print_options *print_options)
+{
+  return {{
+    { {shadowstack_print_option_defs}, print_options }
+  }};
+}
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 5c3ba80974e..2d33efafd22 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -36,4 +36,23 @@ void shadow_stack_push (gdbarch *gdbarch, regcache *regcache,
 value * dwarf2_prev_ssp (const frame_info_ptr &this_frame,
 			 void **this_cache, int regnum);
 
+/* Data for the printing of shadow stack frame information, exposed as
+   command options.  */
+
+struct shadow_stack_print_options
+{
+  const char *print_frame_info = print_frame_info_auto;
+};
+
+/* Implementation of "backtrace shadow" comand.  */
+
+void backtrace_shadow_command (const char *arg, int from_tty);
+
+/* Create an option_def_group array grouping all the "backtrace shadow"
+   options, with SSP_OPTS as contexts.  */
+
+std::array<gdb::option::option_def_group, 1>
+  make_backtrace_shadow_options_def_group
+    (shadow_stack_print_options *print_options);
+
 #endif /* GDB_SHADOW_STACK_H */
diff --git a/gdb/stack.c b/gdb/stack.c
index 40756f74a00..30a7d8621be 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -50,6 +50,7 @@
 #include "linespec.h"
 #include "cli/cli-utils.h"
 #include "objfiles.h"
+#include "shadow-stack.h"
 
 #include "symfile.h"
 #include "extension.h"
@@ -86,7 +87,7 @@ const char print_frame_info_source_and_location[] = "source-and-location";
 const char print_frame_info_location_and_address[] = "location-and-address";
 const char print_frame_info_short_location[] = "short-location";
 
-static const char *const print_frame_info_choices[] =
+const char *const print_frame_info_choices[] =
 {
   print_frame_info_auto,
   print_frame_info_source_line,
@@ -962,11 +963,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++)
@@ -1016,7 +1015,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
@@ -1025,14 +1024,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)
@@ -1298,7 +1292,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;
@@ -1364,31 +1358,28 @@ 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
-print_lib (ui_out *uiout, const char *lib)
+void
+print_lib (ui_out *uiout, const char *lib, bool shadowstack_frame)
 {
-  annotate_frame_where ();
+  annotate_frame_where (shadowstack_frame);
   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)
+void
+print_filename (ui_out *uiout, symtab_and_line sal, bool shadowstack_frame)
 {
-  annotate_frame_source_begin ();
+  annotate_frame_source_begin (shadowstack_frame);
   const char *filename_display;
 
   filename_display = symtab_to_filename_for_display (sal.symtab);
   uiout->wrap_hint (3);
   uiout->text (" at ");
-  annotate_frame_source_file ();
+  annotate_frame_source_file (shadowstack_frame);
+
   uiout->field_string ("file", filename_display, file_name_style.style ());
 
   if (uiout->is_mi_like_p ())
@@ -1397,21 +1388,21 @@ print_filename (ui_out *uiout, symtab_and_line sal)
       uiout->field_string ("fullname", fullname);
     }
 
-  annotate_frame_source_file_end ();
+  annotate_frame_source_file_end (shadowstack_frame);
   uiout->text (":");
-  annotate_frame_source_line ();
+  annotate_frame_source_line (shadowstack_frame);
   uiout->field_signed ("line", sal.line, line_number_style.style ());
-  annotate_frame_source_end ();
+  annotate_frame_source_end (shadowstack_frame);
 }
 
-/*  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)
+	       gdb::unique_xmalloc_ptr<char> const &funname,
+	       bool shadowstack_frame)
 {
-  annotate_frame_function_name ();
+  annotate_frame_function_name (shadowstack_frame);
   string_file stb;
   gdb_puts (funname ? funname.get () : "??", &stb);
   uiout->field_stream ("func", stb, function_name_style.style ());
@@ -2223,6 +2214,23 @@ backtrace_command (const char *arg, int from_tty)
   backtrace_command_1 (fp_opts, bt_cmd_opts, arg, from_tty);
 }
 
+/* Completer for the "backtrace shadow" sub-command.  */
+
+static void
+backtrace_shadow_command_completer (struct cmd_list_element *ignore,
+				    completion_tracker &tracker,
+				    const char *text, const char */*word*/)
+{
+  const auto group
+    = make_backtrace_shadow_options_def_group (nullptr);
+  if (gdb::option::complete_options
+      (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group))
+    return;
+
+  const char *word = advance_to_expression_complete_word_point (tracker, text);
+  expression_completer (ignore, tracker, text, word);
+}
+
 /* Completer for the "backtrace" command.  */
 
 static void
@@ -3314,6 +3322,9 @@ static struct cmd_list_element *select_frame_cmd_list = NULL;
 /* Commands with a prefix of `info frame'.  */
 static struct cmd_list_element *info_frame_cmd_list = NULL;
 
+/* Commands with a prefix of `backtrace' of `bt'.  */
+static cmd_list_element *bt_list = nullptr;
+
 INIT_GDB_FILE (stack)
 {
   struct cmd_list_element *cmd;
@@ -3503,10 +3514,31 @@ With a negative COUNT, print outermost -COUNT frames."),
 			       backtrace_opts);
 
   cmd_list_element *backtrace_cmd
-    = add_com ("backtrace", class_stack, backtrace_command,
-	       backtrace_help.c_str ());
+    = add_prefix_cmd ("backtrace", class_stack, backtrace_command,
+		      backtrace_help.c_str (), &bt_list, 1, &cmdlist);
   set_cmd_completer_handle_brkchars (backtrace_cmd, backtrace_command_completer);
 
+  const auto backtrace_shadow_opts
+    = make_backtrace_shadow_options_def_group (nullptr);
+
+  static std::string backtrace_shadow_help
+    = gdb::option::build_help (_("\
+Print backtrace of all shadow stack frames, or innermost COUNT frames.\n\
+Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]\n\
+\n\
+Options:\n\
+%OPTIONS%\n\
+\n\
+With a negative COUNT, print outermost -COUNT elements."),
+			       backtrace_shadow_opts);
+
+  cmd_list_element *bt_shadow_cmd = add_cmd ("shadow", class_stack,
+					     backtrace_shadow_command,
+					     backtrace_shadow_help.c_str (),
+					     &bt_list);
+  set_cmd_completer_handle_brkchars
+    (bt_shadow_cmd, backtrace_shadow_command_completer);
+
   add_com_alias ("bt", backtrace_cmd, class_stack, 0);
 
   add_com_alias ("where", backtrace_cmd, class_stack, 0);
diff --git a/gdb/stack.h b/gdb/stack.h
index 6b2efb8fe60..966cf5caed0 100644
--- a/gdb/stack.h
+++ b/gdb/stack.h
@@ -84,4 +84,59 @@ void frame_apply_all_cmd_completer (struct cmd_list_element *ignore,
 				    completion_tracker &tracker,
 				    const char *text, const char */*word*/);
 
+/* Print the filenname of SAL to UIOUT for the printing of frame
+   information.  In case SHADOWSTACK_FRAME is true, we annotate for a
+   shadow stack frame.  If it is false (default), we annotate for a
+   normal frame.  */
+
+void print_filename (ui_out *uiout, symtab_and_line sal,
+		     bool shadowstack_frame = false);
+
+/* Print the library LIB to UIOUT for the printing of frame
+   information.  In case SHADOWSTACK_FRAME is true, we annotate for a
+   shadow stack frame.  If it is false (default), we annotate for a
+   normal frame.  */
+
+void print_lib (ui_out *uiout, const char *lib,
+		bool shadowstack_frame = false);
+
+/* If available, print FUNNAME to UIOUT for the printing of frame
+   information.  In case SHADOWSTACK_FRAME is true, we annotate for a
+   shadow stack frame.  If it is false (default), we annotate for a
+   normal frame.  */
+
+void print_funname (ui_out *uiout,
+		    gdb::unique_xmalloc_ptr<char> const &funname,
+		    bool shadowstack_frame = false);
+
+/* 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);
+
+/* The possible choices of "set print frame-info".  */
+extern const char *const print_frame_info_choices[7];
+
 #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 0ae172d7c41..4563e49d9e4 100644
--- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
@@ -109,6 +109,94 @@ 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 frame0 "#0\[ \t\]*$sspval_call2 in call1$fill"
+    set frame1 "#1\[ \t\]*$sspval_call1 in main$fill"
+    set frame2 "#2\[ \t\]*$sspval_main$fill"
+
+    # We can 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" \
+	[multi_line \
+	    "$frame0" \
+	    "$frame1" \
+	    "$frame2" \
+	    ".*" ] \
+	"test shadow stack backtrace until the bottom of the stack."
+
+    gdb_test "bt shadow 2" \
+	[multi_line \
+	    "$frame0" \
+	    "$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 \
+	    "$frame0" \
+	    "$frame1" ] \
+	"test shadow stack backtrace with limit"
+    gdb_test_no_output "set backtrace limit unlimited" "restore backtrace limit default"
+
+    # Test annotations for bt shadow.
+    save_vars { ::gdb_prompt } {
+
+	# Just like for other gdb tests testing annotations, note:
+	# When this prompt is in use the gdb_test procedure cannot be used
+	# because it assumes that the last char of the gdb_prompt is a
+	# white space.  This is not true with this annotated prompt.
+	# One alternative is to use gdb_test_multiple.
+	set ::gdb_prompt "\r\n\032\032pre-prompt\r\n$::gdb_prompt \r\n\032\032prompt\r\n"
+
+	gdb_test_multiple "set annotate 2" "set annotate 2" {
+	    -re "set annotate 2\r\n$gdb_prompt$" {
+		pass "annotation set at level 2"
+	    }
+	    -re ".*$gdb_prompt$" {
+		fail "annotation set at level 2"
+	    }
+	    timeout {
+		fail "annotation set at level 2 (timeout)"
+	    }
+	}
+
+	set inner_frame_re [multi_line \
+	    "shadow-stack-frame-address" \
+	    "$hex\r\n\032\032shadow-stack-frame-address-end" \
+	    "\ in \r\n\032\032shadow-stack-frame-function-name" \
+	    "$fill\r\n\032\032shadow-stack-frame-source-begin" \
+	    " at \r\n\032\032shadow-stack-frame-source-file\r\n$fill" \
+	    "\032\032shadow-stack-frame-source-file-end" \
+	    ":\r\n\032\032shadow-stack-frame-source-line\r\n$decimal" \
+	    "\032\032shadow-stack-frame-source-end\r\n\r\n\r\n"]
+
+	set re [multi_line \
+	    "shadow-stack-frame-begin 0 $hex" \
+	    "#0  \r\n\032\032$inner_frame_re\032\032shadow-stack-frame-end" \
+	    "\r\n\032\032shadow-stack-frame-begin 1 $hex" \
+	    "#1  \r\n\032\032$inner_frame_re\032\032shadow-stack-frame-end" \
+	    "\\(More shadow stack frames follow...\\)" \
+	    "$gdb_prompt$" ]
+
+	gdb_test_multiple "backtrace shadow 2" "backtrace shadow with annotations" {
+	    -re $re {
+		pass $gdb_test_name
+	    }
+	}
+    }
+    gdb_test "set annotate 0" ".*post-prompt.*" "set annotate 0"
+
     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/help.exp b/gdb/testsuite/gdb.base/help.exp
index ec41382a9af..8fbc6005232 100644
--- a/gdb/testsuite/gdb.base/help.exp
+++ b/gdb/testsuite/gdb.base/help.exp
@@ -168,7 +168,11 @@ gdb_test "apropos An alias of command backtrace with arg 10" \
     "mybt10 -- An alias of command backtrace with arg 10\." \
     "apropos after documenting aliases showing mybt10 doc"
 
-gdb_test "help mybt" " alias mybt = backtrace \[\r\n\]+An alias of command backtrace without any args\." \
+set bt_alias_doc "alias mybt = backtrace \[\r\n\]+An alias of command backtrace without any args\."
+set bt_shadow_doc "\[\r\n\]+List of \"backtrace\" subcommands:\[\r\n\]+backtrace shadow -- Print backtrace of all shadow stack frames, or innermost COUNT frames."
+set bt_shadow_help_doc "\[\r\n\]+Type \"help backtrace\" followed by subcommand name for full documentation."
+
+gdb_test "help mybt" "$bt_alias_doc$bt_shadow_doc$bt_shadow_help_doc.*" \
     "help mybt after documenting aliases showing mybt doc"
 
 # Check pre-defined aliases cannot be documented.
diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp
index 8ac9f4ee755..350bf6205b8 100644
--- a/gdb/testsuite/gdb.base/options.exp
+++ b/gdb/testsuite/gdb.base/options.exp
@@ -288,11 +288,16 @@ proc_with_prefix test-backtrace {} {
     clean_restart
 
     test_gdb_complete_unique "backtrace" "backtrace"
-    test_gdb_complete_none "backtrace "
+    test_gdb_complete_unique "backtrace shadow" "backtrace shadow"
+    test_gdb_complete_none "backtrace shadow "
 
     gdb_test "backtrace -" "Ambiguous option at: -"
     gdb_test "backtrace --" "No stack\\."
     gdb_test "backtrace -- -" "No stack\\."
+    gdb_test "backtrace shadow -" \
+	"Requires an argument. Valid arguments are auto, source-line.*\\."
+    gdb_test "backtrace shadow --" "No shadow stack\\."
+    gdb_test "backtrace shadow -- -" "No shadow stack\\."
 
     test_gdb_complete_multiple "backtrace " "-" "" {
 	"-entry-values"
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (5 preceding siblings ...)
  2025-09-23 11:18 ` [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-09-23 11:49   ` Eli Zaretskii
  2025-09-23 11:18 ` [PATCH 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

On x86 with CET there can be elements on the shadow stack which are not
return addresses.  In this case, we just want to print the element itself
in the shadow stack backtrace, but no further information.

Provide a gdbarch hook to distinguish between return and non-return
addresses and use it to print the shadow stack backtrace as described
above.
---
 gdb/doc/gdb.texinfo       | 19 ++++++++++++
 gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
 gdb/gdbarch-gen.h         | 15 +++++++++
 gdb/gdbarch.h             |  1 +
 gdb/gdbarch_components.py | 17 +++++++++++
 gdb/shadow-stack.c        | 64 +++++++++++++++++----------------------
 gdb/shadow-stack.h        | 37 ++++++++++++++++++++++
 7 files changed, 148 insertions(+), 37 deletions(-)

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index ebda4546b58..a0fde385a8e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8887,6 +8887,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.  For such shadow stack elements, the
+shadow stack frame just contains the level and the address on the shadow
+stack, as shown in the following example by frame 1:
+
+@smallexample
+@group
+(gdb) bt shadow 4
+#0  0x00007ffff7c54d90 in __restore_rt from /lib64/libc.so.6
+#1  0x80007ffff79fffd8
+#2  0x00007ffff7c54ce6 in __GI_raise at ../sysdeps/posix/raise.c:27
+#3  0x000000000040115d in main at /tmp/amd64-shadow-stack-signal.c:32
+(More shadow stack frames follow...)
+@end group
+@end smallexample
+
+
 @node Selection
 @section Selecting a Frame
 
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index 640576f4b85..21def8fd3e7 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -266,6 +266,7 @@ struct gdbarch
   gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr;
   gdbarch_address_in_shadow_stack_memory_range_ftype *address_in_shadow_stack_memory_range = 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
@@ -543,6 +544,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of top_addr_empty_shadow_stack, has predicate.  */
   /* Skip verify of address_in_shadow_stack_memory_range, 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 ());
@@ -1432,6 +1434,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 != NULL)
     gdbarch->dump_tdep (gdbarch, file);
 }
@@ -5659,3 +5667,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 != NULL);
+  return gdbarch->is_no_return_shadow_stack_address != NULL;
+}
+
+bool
+gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const shadow_stack_frame_info &frame)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->is_no_return_shadow_stack_address != NULL);
+  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, frame);
+}
+
+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 34a32c346e8..0b92a448c78 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1856,3 +1856,18 @@ extern void set_gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gd
 
 extern int gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch);
 extern void set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch, int shadow_stack_element_size_aligned);
+
+/* 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 'backtrace shadow' and the
+   shadow stack can contain elements which are not return addresses, this
+   function has to be provided.
+
+   Return true, if FRAME does not contain a return address in FRAME->VALUE
+   but another valid value for the architecture's shadow stack. */
+
+extern bool gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch);
+
+typedef bool (gdbarch_is_no_return_shadow_stack_address_ftype) (struct gdbarch *gdbarch, const shadow_stack_frame_info &frame);
+extern bool gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const shadow_stack_frame_info &frame);
+extern 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.h b/gdb/gdbarch.h
index 20497612811..57b8f021e93 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -31,6 +31,7 @@
 #include "gdbsupport/gdb-checked-static-cast.h"
 #include "registry.h"
 #include "solib.h"
+#include "shadow-stack.h"
 
 struct floatformat;
 struct ui_file;
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index 5223553941f..d925f5a7e87 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2938,3 +2938,20 @@ this value.
     predefault="8",
     invalid=False,
 )
+
+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 'backtrace shadow' and the
+shadow stack can contain elements which are not return addresses, this
+function has to be provided.
+
+Return true, if FRAME does not contain a return address in FRAME->VALUE
+but another valid value for the architecture's shadow stack.
+""",
+    type="bool",
+    name="is_no_return_shadow_stack_address",
+    params=[("const shadow_stack_frame_info &", "frame")],
+    predicate=True,
+)
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 759ef6989ea..42032f51781 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -222,43 +222,6 @@ pc_in_middle_of_statement (CORE_ADDR pc, symtab_and_line sal)
   return pc > sal.pc && pc <= sal.end;
 }
 
-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
-    (gdbarch *gdbarch, 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;
-  /* 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;
-};
-
 static
 gdb::unique_xmalloc_ptr<char> find_pc_funname (CORE_ADDR pc)
 {
@@ -285,6 +248,33 @@ do_print_shadow_stack_frame_info
    const shadow_stack_print_options &print_options,
    const shadow_stack_frame_info &frame, print_what print_what)
 {
+  if (gdbarch_is_no_return_shadow_stack_address_p (gdbarch)
+      && gdbarch_is_no_return_shadow_stack_address (gdbarch, frame))
+    {
+      /* It is possible, for the x86 architecture for instance, that an
+	 element on the shadow stack is not a return address.  We still
+	 want to print the address in that case but no further
+	 information.  */
+      ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame");
+      uiout->text ("#");
+      uiout->field_fmt_signed (2, ui_left, "level", frame.level);
+
+      /* 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 (gdbarch);
+
+      uiout->field_string
+	("addr", hex_string_custom (frame.value, element_size * 2),
+	 address_style.style ());
+      uiout->text ("\n");
+      gdb_flush (gdb_stdout);
+      return;
+    }
+
   if (print_options.print_frame_info != print_frame_info_auto)
     {
       /* Use the specific frame information desired by the user.  */
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 2d33efafd22..f801d077820 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -55,4 +55,41 @@ std::array<gdb::option::option_def_group, 1>
   make_backtrace_shadow_options_def_group
     (shadow_stack_print_options *print_options);
 
+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
+    (gdbarch *gdbarch, 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;
+  /* 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;
+};
+
 #endif /* GDB_SHADOW_STACK_H */
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux.
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (6 preceding siblings ...)
  2025-09-23 11:18 ` [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-11-26  4:22   ` Thiago Jung Bauermann
  2025-09-23 11:18 ` [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
  2025-09-25 11:46 ` [PATCH 0/9] Add new command to print the shadow stack backtrace Schimpe, Christina
  9 siblings, 1 reply; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

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/next/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                        | 43 +++++++++++++++
 .../amd64-shadow-stack-backtrace-signal.exp   | 54 +++++++++++++++++++
 .../gdb.arch/amd64-shadow-stack-signal.c      | 31 +++++++++++
 3 files changed, 128 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 f0db3b7a1b4..d72525a4cab 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -1952,6 +1952,46 @@ amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
   return ssp;
 }
 
+/* Return true, if FRAME is a valid shadow stack frame while FRAME.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.  */
+
+static bool
+amd64_linux_is_no_return_shadow_stack_address
+  (gdbarch *gdbarch, const shadow_stack_frame_info &frame)
+{
+  /* FRAME must be a valid shadow stack frame.  */
+  std::pair<CORE_ADDR, CORE_ADDR> range;
+  gdb_assert (gdbarch_address_in_shadow_stack_memory_range (gdbarch,
+							    frame.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 ((frame.value & mask) == 0)
+    return false;
+
+  /* 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 = (frame.value & (~mask));
+
+  /* Compute the previous/old SSP.  The shadow stack grows downwards.  To
+     compute the previous shadow stack pointer, we need to increment
+     FRAME.SSP.  */
+  CORE_ADDR prev_ssp
+    = frame.ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch);
+
+  /* We incremented FRAME.SSP by one element to compute PREV_SSP before.
+     In case FRAME.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);
+
+  return (shadow_stack_val_cleared == prev_ssp);
+}
+
 static void
 amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
 			     int num_disp_step_buffers)
@@ -2012,6 +2052,9 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
 
   set_gdbarch_get_shadow_stack_pointer (gdbarch,
 					amd64_linux_get_shadow_stack_pointer);
+
+  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..727bc87f632
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
@@ -0,0 +1,54 @@
+# Copyright 2024-2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <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"
+
+    # Create shadow stack frame based on ssp in frame 2 and with bit 63 set.
+    gdb_test "frame 2" ".*" "move to frame 2"
+    set ssp_frame2 [get_hexadecimal_valueof "\$pl3_ssp" ""]
+    set ssp_frame2 [format 0x%x [expr (1 << 63) | $ssp_frame2]]
+
+    # Test shadow stack backtrace including ssp of frame 2 with bit 63 set.
+    gdb_test "bt shadow" \
+	[multi_line \
+	    "#0\[ \t\]*$hex in \[^\r\n\]+" \
+	    "#1\[ \t\]*$ssp_frame2" \
+	    "#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..f3aff8fc19c
--- /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 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: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (7 preceding siblings ...)
  2025-09-23 11:18 ` [PATCH 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
@ 2025-09-23 11:18 ` Christina Schimpe
  2025-09-23 11:53   ` Eli Zaretskii
                     ` (2 more replies)
  2025-09-25 11:46 ` [PATCH 0/9] Add new command to print the shadow stack backtrace Schimpe, Christina
  9 siblings, 3 replies; 67+ messages in thread
From: Christina Schimpe @ 2025-09-23 11:18 UTC (permalink / raw)
  To: gdb-patches

Add the mi command for the subcommand "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                                      |  10 ++
 gdb/doc/gdb.texinfo                           |  47 ++++++
 gdb/mi/mi-cmd-stack.c                         | 146 ++++++++++++++++++
 gdb/mi/mi-cmds.c                              |   2 +
 gdb/mi/mi-cmds.h                              |   1 +
 gdb/shadow-stack.c                            |  17 +-
 gdb/shadow-stack.h                            |  11 ++
 .../gdb.mi/mi-shadow-stack-signal.exp         |  69 +++++++++
 gdb/testsuite/gdb.mi/mi-shadow-stack.exp      |  65 ++++++++
 9 files changed, 365 insertions(+), 3 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 eb96ff2ae08..3c52f88b71d 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -40,6 +40,16 @@ bt shadow [option]... [count | -count]
   Print backtrace of all shadow stack frames, or innermost 'count' frames.
   The command is a subcommand of the ordinary backtrace command.
 
+* New MI commands
+
+-shadow-stack-list-frames
+
+  Added new MI command '-shadow-stack-list-frames' which is equivalent to
+  the CLI subcommand 'backtrace shadow' but supports 'low-frame' and
+  'high-frame' as command line parameters.  The parameters are used to
+  print shadow stack frames 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 a0fde385a8e..aa8a33902b5 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -35308,6 +35308,53 @@ 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 -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 -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="0x00007ffff7c54d90",
+  func="__restore_rt",from="/lib64/libc.so.6",arch="i386:x86-64"@},
+shadow-stack-frame=@{level="1",addr="0x80007ffff79fffd8",arch="i386:x86-64"@},
+shadow-stack-frame=@{level="2",addr="0x00007ffff7c54ce6",
+  func="__GI_raise",file="../sysdeps/posix/raise.c",
+  fullname="/usr/src/debug/glibc-2.34-90.bkc.el9.x86_64/sysdeps/posix/raise.c",
+  line="27",arch="i386:x86-64"@}]
+(gdb)
+@end smallexample
+
+For frame 1 we can see that we only print @var{level}, @var{addr} and
+@var{arch}.  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 52ae11b39af..08f43f023f6 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 "gdbarch.h"
+#include "gdbcore.h"
+#include "arch-utils.h"
 
 enum what_to_list { locals, arguments, all };
 
@@ -773,3 +776,146 @@ mi_cmd_stack_info_frame (const char *command, const char *const *argv,
   print_frame_info (user_frame_print_options,
 		    get_selected_frame (NULL), 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)
+{
+  /* There should either be low - high range, or no arguments.  */
+  if ((argc != 0) && (argc != 2))
+    error (_("-shadow-stack-list-frames: Usage: [FRAME_LOW FRAME_HIGH]"));
+
+  /* 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 = "-shadow-stack-list-frames: 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);
+
+  if (!target_has_stack ())
+    error (_("-shadow-stack-list-frames: No shadow stack."));
+
+  gdbarch *gdbarch = get_current_arch ();
+  if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch))
+    error (_("-shadow-stack-list-frames: Printing of 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-list-frames: Shadow stack is not enabled for \
+	      the current target."));
+
+  ui_out_emit_list list_emitter (current_uiout, "shadow-stack");
+
+  /* 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.  */
+  std::pair<CORE_ADDR, CORE_ADDR> range;
+  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
+						     &range))
+    {
+      /* If START_SSP points off the shadow stack memory range, we cannot
+	 print the shadow stack backtrace.  This is possible, for
+	 instance, on x86 if NEW_SSP points to the end of RANGE which
+	 means that the shadow stack is empty.  */
+      return;
+    }
+
+  /* For ARM's Guarded Control Stack, if START_SSP points one element
+     before the end of RANGE, it means that the shadow stack pointer
+     is valid but the shadow stack is empty.  */
+   if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
+       && gdbarch_top_addr_empty_shadow_stack (gdbarch, *start_ssp, range))
+     return;
+
+  std::optional<shadow_stack_frame_info> curr;
+  CORE_ADDR new_value;
+  const int addr_size_byte = gdbarch_addr_bit (gdbarch) / 8;
+  const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  if (!safe_read_memory_unsigned_integer (*start_ssp, addr_size_byte,
+					  byte_order, &new_value))
+    error (_("-shadow-stack-list-frames: Cannot read shadow stack memory."));
+
+  curr = {*start_ssp, new_value, 0, ssp_unwind_stop_reason::no_error};
+
+  /* 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 (gdbarch, range);
+
+  if (!curr.has_value ())
+    error (_("-shadow-stack-list-frames: Not enough frames on the shadow \
+	      stack."));
+
+  shadow_stack_print_options print_options;
+  print_options.print_frame_info
+    = user_frame_print_options.print_frame_info;
+
+  /* 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 (gdbarch, print_options, *curr,
+				     LOCATION);
+      curr = curr->unwind_prev_shadow_stack_frame_info (gdbarch, range);
+    }
+}
diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c
index abd3fb9b53d..8125891a9cc 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 990ff75485c..e3a6f3e8be2 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 42032f51781..ef8e31c730e 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -270,6 +270,12 @@ do_print_shadow_stack_frame_info
       uiout->field_string
 	("addr", hex_string_custom (frame.value, element_size * 2),
 	 address_style.style ());
+
+      if (uiout->is_mi_like_p ())
+	{
+	  uiout->field_string
+	    ("arch", (gdbarch_bfd_arch_info (gdbarch))->printable_name);
+	}
       uiout->text ("\n");
       gdb_flush (gdb_stdout);
       return;
@@ -333,6 +339,12 @@ do_print_shadow_stack_frame_info
 	    if (lib != nullptr)
 	      print_lib (uiout, lib, true);
 	  }
+
+	if (uiout->is_mi_like_p ())
+	  {
+	    uiout->field_string
+	      ("arch", (gdbarch_bfd_arch_info (gdbarch))->printable_name);
+	  }
       } /* Extra scope to print frame tuple.  */
 
       uiout->text ("\n");
@@ -355,10 +367,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
   (gdbarch *gdbarch, const shadow_stack_print_options &print_options,
    const shadow_stack_frame_info &frame, print_what print_what)
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index f801d077820..1b0413df26d 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -92,4 +92,15 @@ class shadow_stack_frame_info
     = ssp_unwind_stop_reason::no_error;
 };
 
+/* 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
+  (gdbarch *gdbarch, const shadow_stack_print_options &print_options,
+   const shadow_stack_frame_info &frame, print_what print_what);
+
 #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..219f1b8ad78
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
@@ -0,0 +1,69 @@
+# Copyright 2024-2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <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 trigged 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\"\,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..bbbc7ec5460
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
@@ -0,0 +1,65 @@
+# Copyright 2024-2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <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"}] } {
+	restart_and_run_infcall_call2
+    }
+
+    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.*$frame3.*" \
+	"test shadow-stack-list-frames"
+
+    # Test low-frame/high-frame
+    mi_gdb_test "231-shadow-stack-list-frames 1 2" \
+	"231\\^done,shadow-stack=\\\[$frame2.*$frame3\\\]" \
+	"test shadow-stack-list-frames low/high-frames"
+
+    mi_gdb_exit
+}
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-23 11:18 ` [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace Christina Schimpe
@ 2025-09-23 11:47   ` Eli Zaretskii
  2025-09-25 11:06     ` Schimpe, Christina
  2025-10-03 20:15   ` Tom Tromey
  2025-10-31  4:02   ` Thiago Jung Bauermann
  2 siblings, 1 reply; 67+ messages in thread
From: Eli Zaretskii @ 2025-09-23 11:47 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

> From: Christina Schimpe <christina.schimpe@intel.com>
> Date: Tue, 23 Sep 2025 11:18:39 +0000
> 
> Add a subcommand 'bt shadow' for the ordinary backtrace command which
> prints the shadow stack backtrace.
> Similar to the ordinary backtrace command 'bt shadow' can be configured
> using COUNT and the command line option -frame-info.  However, we always
> print the address and the command is not affected by the setting
> "print address" as well as the setting "print frame-info location-and-address".
> Also we do not print the frame arguments.
> 
> Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]
> 
> Help output:
> ~~
> (gdb) help bt shadow
> Print backtrace of all shadow stack frames, or innermost COUNT frames.
> Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]

Thanks, but I wonder if this UI is the best we can come up with.
The "backtrace" command doesn't currently have non-option arguments
except COUNT.  We have just got rid of qualifier arguments like "full"
and "hide".  Instead of introducing qualifier arguments anew, why not
go the way of thread-related commands and add a new command "shadow",
so the user could say "shadow backtrace" and maybe in the future also
other sub-commands?

Also, a question: can I say something like "thread apply 1-10 bt shadow"?

> +* New commands
> +
> +backtrace shadow [option]... [count | -count]
> +bt shadow [option]... [count | -count]
> +  Print backtrace of all shadow stack frames, or innermost 'count' frames.
> +  The command is a subcommand of the ordinary backtrace command.
> +
>  *** Changes in GDB 17

This part is okay (subject to the general comment above).

> +This is how a shadow stack backtrace looks like on amd64:
> +@smallexample
> +@group
> +#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
> +#1  0x000000000040112f in main at amd64-shadow-stack.c:21
> +#2  0x00007ffff7c3fe70 in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58

This line is too long.  Since this is just an example, I suggest to
make it artificially shorter by editing the names of the functions and
file names.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2025-09-23 11:18 ` [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
@ 2025-09-23 11:49   ` Eli Zaretskii
  2025-09-25 11:10     ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Eli Zaretskii @ 2025-09-23 11:49 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

> From: Christina Schimpe <christina.schimpe@intel.com>
> Date: Tue, 23 Sep 2025 11:18:40 +0000
> 
> On x86 with CET there can be elements on the shadow stack which are not
> return addresses.  In this case, we just want to print the element itself
> in the shadow stack backtrace, but no further information.
> 
> Provide a gdbarch hook to distinguish between return and non-return
> addresses and use it to print the shadow stack backtrace as described
> above.
> ---
>  gdb/doc/gdb.texinfo       | 19 ++++++++++++
>  gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
>  gdb/gdbarch-gen.h         | 15 +++++++++
>  gdb/gdbarch.h             |  1 +
>  gdb/gdbarch_components.py | 17 +++++++++++
>  gdb/shadow-stack.c        | 64 +++++++++++++++++----------------------
>  gdb/shadow-stack.h        | 37 ++++++++++++++++++++++
>  7 files changed, 148 insertions(+), 37 deletions(-)

Thanks.

> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index ebda4546b58..a0fde385a8e 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -8887,6 +8887,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
               ^
A cross-reference is missing here.

> +in a special format with bit 63 set.  For such shadow stack elements, the
> +shadow stack frame just contains the level and the address on the shadow
> +stack, as shown in the following example by frame 1:
> +
> +@smallexample
> +@group
> +(gdb) bt shadow 4
> +#0  0x00007ffff7c54d90 in __restore_rt from /lib64/libc.so.6
> +#1  0x80007ffff79fffd8
> +#2  0x00007ffff7c54ce6 in __GI_raise at ../sysdeps/posix/raise.c:27
> +#3  0x000000000040115d in main at /tmp/amd64-shadow-stack-signal.c:32
> +(More shadow stack frames follow...)
> +@end group
> +@end smallexample

Would it make sense to show something like "<signal caught>", instead
of a frame with only an address?

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2025-09-23 11:18 ` [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
@ 2025-09-23 11:53   ` Eli Zaretskii
  2025-09-25 11:32     ` Schimpe, Christina
  2025-10-03 20:17   ` Tom Tromey
  2025-11-26  4:26   ` Thiago Jung Bauermann
  2 siblings, 1 reply; 67+ messages in thread
From: Eli Zaretskii @ 2025-09-23 11:53 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

> From: Christina Schimpe <christina.schimpe@intel.com>
> Date: Tue, 23 Sep 2025 11:18:42 +0000
> 
> Add the mi command for the subcommand "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                                      |  10 ++
>  gdb/doc/gdb.texinfo                           |  47 ++++++
>  gdb/mi/mi-cmd-stack.c                         | 146 ++++++++++++++++++
>  gdb/mi/mi-cmds.c                              |   2 +
>  gdb/mi/mi-cmds.h                              |   1 +
>  gdb/shadow-stack.c                            |  17 +-
>  gdb/shadow-stack.h                            |  11 ++
>  .../gdb.mi/mi-shadow-stack-signal.exp         |  69 +++++++++
>  gdb/testsuite/gdb.mi/mi-shadow-stack.exp      |  65 ++++++++
>  9 files changed, 365 insertions(+), 3 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
>  create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack.exp

Thanks.

> diff --git a/gdb/NEWS b/gdb/NEWS
> index eb96ff2ae08..3c52f88b71d 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -40,6 +40,16 @@ bt shadow [option]... [count | -count]
>    Print backtrace of all shadow stack frames, or innermost 'count' frames.
>    The command is a subcommand of the ordinary backtrace command.
>  
> +* New MI commands
> +
> +-shadow-stack-list-frames
> +
> +  Added new MI command '-shadow-stack-list-frames' which is equivalent to
> +  the CLI subcommand 'backtrace shadow' but supports 'low-frame' and
> +  'high-frame' as command line parameters.  The parameters are used to
> +  print shadow stack frames between certain levels on the shadow stack
> +  only.

So we will support in MI features that are not supported by the CLI?
Is that limitation really necessary?

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-23 11:47   ` Eli Zaretskii
@ 2025-09-25 11:06     ` Schimpe, Christina
  2025-09-25 13:19       ` Eli Zaretskii
  2025-10-13  1:17       ` Thiago Jung Bauermann
  0 siblings, 2 replies; 67+ messages in thread
From: Schimpe, Christina @ 2025-09-25 11:06 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, Thiago Jung Bauermann

Hi Eli, 

Thanks for the quick feedback! Please find my comments below.

> -----Original Message-----
> From: Eli Zaretskii <eliz@gnu.org>
> Sent: Tuesday, September 23, 2025 1:47 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> > From: Christina Schimpe <christina.schimpe@intel.com>
> > Date: Tue, 23 Sep 2025 11:18:39 +0000
> >
> > Add a subcommand 'bt shadow' for the ordinary backtrace command which
> > prints the shadow stack backtrace.
> > Similar to the ordinary backtrace command 'bt shadow' can be
> > configured using COUNT and the command line option -frame-info.
> > However, we always print the address and the command is not affected
> > by the setting "print address" as well as the setting "print frame-info
> location-and-address".
> > Also we do not print the frame arguments.
> >
> > Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]
> >
> > Help output:
> > ~~
> > (gdb) help bt shadow
> > Print backtrace of all shadow stack frames, or innermost COUNT frames.
> > Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
> 
> Thanks, but I wonder if this UI is the best we can come up with.
> The "backtrace" command doesn't currently have non-option arguments
> except COUNT. 

Hm, not sure if I fully understand what you mean by non-option arguments.
"bt shadow" does not introduce more non-option arguments, except if you say
that "shadow" itself is interpreted as "non-option" argument.

> We have just got rid of qualifier arguments like "full"
> and "hide".  Instead of introducing qualifier arguments anew, why not go the
> way of thread-related commands and add a new command "shadow", so the
> user could say "shadow backtrace" and maybe in the future also other sub-
> commands?

This has been discussed in a thread years ago:
https://sourceware.org/pipermail/gdb/2023-December/051024.html
The direction was to make it part of the ordinary bt command. 

Currently, we don't plan any further commands for the shadow stack. But
maybe for ARM's Guarded Control Stack any further options are planned?
I added Thiago in cc here. Maybe he has some more input.

> Also, a question: can I say something like "thread apply 1-10 bt shadow"?

Yes, this is possible:
~~~
(gdb) thread apply all bt shadow

Thread 2.1 (Thread 0x7ffff7fae740 (LWP 403202) "sample"):
#0  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:74
#1  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:128
#2  0x0000555555555065 in _start

Thread 1.1 (Thread 0x7ffff7fae740 (LWP 403193) "sample"):
#0  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:74
#1  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:128
#2  0x0000555555555065 in _start
~~~

> > +* New commands
> > +
> > +backtrace shadow [option]... [count | -count] bt shadow [option]...
> > +[count | -count]
> > +  Print backtrace of all shadow stack frames, or innermost 'count' frames.
> > +  The command is a subcommand of the ordinary backtrace command.
> > +
> >  *** Changes in GDB 17
> 
> This part is okay (subject to the general comment above).
> 
> > +This is how a shadow stack backtrace looks like on amd64:
> > +@smallexample
> > +@group
> > +#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
> > +#1  0x000000000040112f in main at amd64-shadow-stack.c:21
> > +#2  0x00007ffff7c3fe70 in __libc_start_call_main at
> > +../sysdeps/nptl/libc_start_call_main.h:58
> 
> This line is too long.  Since this is just an example, I suggest to make it artificially
> shorter by editing the names of the functions and file names.

I agree and will fix. 

Kind Regards
Christina

> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2025-09-23 11:49   ` Eli Zaretskii
@ 2025-09-25 11:10     ` Schimpe, Christina
  2025-11-02 21:20       ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-09-25 11:10 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, Thiago Jung Bauermann

Hi Eli, 

> -----Original Message-----
> From: Eli Zaretskii <eliz@gnu.org>
> Sent: Tuesday, September 23, 2025 1:50 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow
> stack backtrace elements.
> 
> > From: Christina Schimpe <christina.schimpe@intel.com>
> > Date: Tue, 23 Sep 2025 11:18:40 +0000
> >
> > On x86 with CET there can be elements on the shadow stack which are
> > not return addresses.  In this case, we just want to print the element
> > itself in the shadow stack backtrace, but no further information.
> >
> > Provide a gdbarch hook to distinguish between return and non-return
> > addresses and use it to print the shadow stack backtrace as described
> > above.
> > ---
> >  gdb/doc/gdb.texinfo       | 19 ++++++++++++
> >  gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
> >  gdb/gdbarch-gen.h         | 15 +++++++++
> >  gdb/gdbarch.h             |  1 +
> >  gdb/gdbarch_components.py | 17 +++++++++++
> >  gdb/shadow-stack.c        | 64 +++++++++++++++++----------------------
> >  gdb/shadow-stack.h        | 37 ++++++++++++++++++++++
> >  7 files changed, 148 insertions(+), 37 deletions(-)
> 
> Thanks.
> 
> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
> > ebda4546b58..a0fde385a8e 100644
> > --- a/gdb/doc/gdb.texinfo
> > +++ b/gdb/doc/gdb.texinfo
> > @@ -8887,6 +8887,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
>                ^
> A cross-reference is missing here.

Thanks will fix.

> > +in a special format with bit 63 set.  For such shadow stack elements,
> > +the shadow stack frame just contains the level and the address on the
> > +shadow stack, as shown in the following example by frame 1:
> > +
> > +@smallexample
> > +@group
> > +(gdb) bt shadow 4
> > +#0  0x00007ffff7c54d90 in __restore_rt from /lib64/libc.so.6
> > +#1  0x80007ffff79fffd8
> > +#2  0x00007ffff7c54ce6 in __GI_raise at ../sysdeps/posix/raise.c:27
> > +#3  0x000000000040115d in main at /tmp/amd64-shadow-stack-
> signal.c:32
> > +(More shadow stack frames follow...)
> > +@end group
> > +@end smallexample
> 
> Would it make sense to show something like "<signal caught>", instead of a
> frame with only an address?

Yeah, this is a good idea, I wondered about a similar thing actually but wanted to discuss
the general direction for handling those specific elements on the shadow stack first.
Maybe there are more options on other architectures that we have to consider.
Let's wait for more feedback on this, I added Thiago again in cc here.

Christina

> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2025-09-23 11:53   ` Eli Zaretskii
@ 2025-09-25 11:32     ` Schimpe, Christina
  0 siblings, 0 replies; 67+ messages in thread
From: Schimpe, Christina @ 2025-09-25 11:32 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

Hi Eli,

Thank you for the review.

> -----Original Message-----
> From: Eli Zaretskii <eliz@gnu.org>
> Sent: Tuesday, September 23, 2025 1:54 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
> 
> > From: Christina Schimpe <christina.schimpe@intel.com>
> > Date: Tue, 23 Sep 2025 11:18:42 +0000
> >
> > Add the mi command for the subcommand "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                                      |  10 ++
> >  gdb/doc/gdb.texinfo                           |  47 ++++++
> >  gdb/mi/mi-cmd-stack.c                         | 146 ++++++++++++++++++
> >  gdb/mi/mi-cmds.c                              |   2 +
> >  gdb/mi/mi-cmds.h                              |   1 +
> >  gdb/shadow-stack.c                            |  17 +-
> >  gdb/shadow-stack.h                            |  11 ++
> >  .../gdb.mi/mi-shadow-stack-signal.exp         |  69 +++++++++
> >  gdb/testsuite/gdb.mi/mi-shadow-stack.exp      |  65 ++++++++
> >  9 files changed, 365 insertions(+), 3 deletions(-)  create mode
> > 100644 gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
> >  create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack.exp
> 
> Thanks.
> 
> > diff --git a/gdb/NEWS b/gdb/NEWS
> > index eb96ff2ae08..3c52f88b71d 100644
> > --- a/gdb/NEWS
> > +++ b/gdb/NEWS
> > @@ -40,6 +40,16 @@ bt shadow [option]... [count | -count]
> >    Print backtrace of all shadow stack frames, or innermost 'count' frames.
> >    The command is a subcommand of the ordinary backtrace command.
> >
> > +* New MI commands
> > +
> > +-shadow-stack-list-frames
> > +
> > +  Added new MI command '-shadow-stack-list-frames' which is
> > + equivalent to  the CLI subcommand 'backtrace shadow' but supports
> > + 'low-frame' and  'high-frame' as command line parameters.  The
> > + parameters are used to  print shadow stack frames between certain
> > + levels on the shadow stack  only.
> 
> So we will support in MI features that are not supported by the CLI?
> Is that limitation really necessary?

The same limitations applies to the mi equivalent of the ordinary backtrace command,
(-stack-list-frames ) so I thought implementing it similar would make sense.

Christina
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (8 preceding siblings ...)
  2025-09-23 11:18 ` [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
@ 2025-09-25 11:46 ` Schimpe, Christina
  2025-10-08  1:46   ` Thiago Jung Bauermann
  9 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-09-25 11:46 UTC (permalink / raw)
  To: Schimpe, Christina, gdb-patches, Thiago Jung Bauermann

Hi Thiago, 

I did not test the "bt shadow" command for ARM's GCS. Does the command make sense for GCS, too ?
If yes, does the implementation I shared here makes sense to you in general?

I tried to address the different Aarch and Intel behavior we discussed here
https://sourceware.org/pipermail/gdb-patches/2025-June/218947.html 
by introducing a new gdbarch hook top_addr_empty_shadow_stack.

Does that go into the right direction for your purposes?

Thanks,
Christina

> -----Original Message-----
> From: Christina Schimpe <christina.schimpe@intel.com>
> Sent: Tuesday, September 23, 2025 1:19 PM
> To: gdb-patches@sourceware.org
> Subject: [PATCH 0/9] Add new command to print the shadow stack backtrace
> 
> Hi all,
> 
> as already indicated in the discussion
> https://sourceware.org/pipermail/gdb/2023-December/051024.html
> I'd like to add a command to print the shadow stack backtrace.
> 
> In the discussion above the main conclusion was to use the ordinary backtrace
> command with a new flag: "bt -shadow".
> I chose a similar direction, but decided against the flag "-shadow"
> in favor of a new subcommand of the ordinary backtrace command:
> "bt shadow".
> 
> This is an example shadow stack backtrace on amd64:
> ~~~
> (gdb) bt shadow
> #0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
> #1  0x000000000040112f in main at amd64-shadow-stack.c:21
> #2  0x00007ffff7c3fe70 in __libc_start_call_main at
> ../sysdeps/nptl/libc_start_call_main.h:58
> #3  0x00007ffff7c3ff20 in __libc_start_main_impl at ../csu/libc-start.c:128
> #4  0x0000000000401045 in _start
> ~~~
> 
> I am happy about your feedback!
> 
> Christina
> 
> 
> Christina Schimpe (9):
>   gdb: Generalize handling of the shadow stack pointer.
>   gdb: Refactor 'stack.c:print_frame'.
>   gdb: Introduce 'stack.c:print_pc' function without frame argument.
>   gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in
>     stack.c.
>   gdb: Refactor 'stack.c:print_frame_info'.
>   gdb: Implement 'bt shadow' to print the shadow stack backtrace.
>   gdb: Provide gdbarch hook to distinguish shadow stack backtrace
>     elements.
>   gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64
>     linux.
>   gdb, mi: Add -shadow-stack-list-frames command
> 
>  gdb/Makefile.in                               |   2 +
>  gdb/NEWS                                      |  17 +
>  gdb/aarch64-linux-tdep.c                      |  51 +-
>  gdb/aarch64-tdep.c                            |  42 +-
>  gdb/amd64-linux-tdep.c                        | 164 ++---
>  gdb/amd64-tdep.c                              |  20 +
>  gdb/annotate.c                                |  93 ++-
>  gdb/annotate.h                                |  18 +-
>  gdb/doc/gdb.texinfo                           | 114 +++
>  gdb/gdbarch-gen.c                             | 158 ++++-
>  gdb/gdbarch-gen.h                             |  80 ++-
>  gdb/gdbarch.h                                 |   1 +
>  gdb/gdbarch_components.py                     | 100 ++-
>  gdb/infcall.c                                 |   4 +-
>  gdb/linux-tdep.c                              |   2 +
>  gdb/mi/mi-cmd-stack.c                         | 146 ++++
>  gdb/mi/mi-cmds.c                              |   2 +
>  gdb/mi/mi-cmds.h                              |   1 +
>  gdb/shadow-stack.c                            | 662 ++++++++++++++++++
>  gdb/shadow-stack.h                            | 106 +++
>  gdb/stack.c                                   | 382 ++++++----
>  gdb/stack.h                                   |  55 ++
>  .../amd64-shadow-stack-backtrace-signal.exp   |  54 ++
>  .../gdb.arch/amd64-shadow-stack-cmds.exp      |  88 +++
>  .../gdb.arch/amd64-shadow-stack-signal.c      |  31 +
>  gdb/testsuite/gdb.base/help.exp               |   6 +-
>  gdb/testsuite/gdb.base/options.exp            |   7 +-
>  .../gdb.mi/mi-shadow-stack-signal.exp         |  69 ++
>  gdb/testsuite/gdb.mi/mi-shadow-stack.exp      |  65 ++
>  29 files changed, 2105 insertions(+), 435 deletions(-)  create mode 100644
> gdb/shadow-stack.c  create mode 100644 gdb/shadow-stack.h  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
>  create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
>  create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack.exp
> 
> --
> 2.34.1
> 
> Intel Deutschland GmbH
> Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
> Tel: +49 89 99 8853-0, www.intel.de
> Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
> Chairperson of the Supervisory Board: Nicole Lau Registered Office: Munich
> Commercial Register: Amtsgericht Muenchen HRB 186928

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-25 11:06     ` Schimpe, Christina
@ 2025-09-25 13:19       ` Eli Zaretskii
  2025-09-25 14:58         ` Simon Marchi
  2025-10-13  1:17       ` Thiago Jung Bauermann
  1 sibling, 1 reply; 67+ messages in thread
From: Eli Zaretskii @ 2025-09-25 13:19 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches, thiago.bauermann

> From: "Schimpe, Christina" <christina.schimpe@intel.com>
> CC: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>, "Thiago Jung
>  Bauermann" <thiago.bauermann@linaro.org>
> Date: Thu, 25 Sep 2025 11:06:32 +0000
> 
> > > (gdb) help bt shadow
> > > Print backtrace of all shadow stack frames, or innermost COUNT frames.
> > > Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
> > 
> > Thanks, but I wonder if this UI is the best we can come up with.
> > The "backtrace" command doesn't currently have non-option arguments
> > except COUNT. 
> 
> Hm, not sure if I fully understand what you mean by non-option arguments.
> "bt shadow" does not introduce more non-option arguments, except if you say
> that "shadow" itself is interpreted as "non-option" argument.

That's what I'm saying, yes.

> > We have just got rid of qualifier arguments like "full"
> > and "hide".  Instead of introducing qualifier arguments anew, why not go the
> > way of thread-related commands and add a new command "shadow", so the
> > user could say "shadow backtrace" and maybe in the future also other sub-
> > commands?
> 
> This has been discussed in a thread years ago:
> https://sourceware.org/pipermail/gdb/2023-December/051024.html
> The direction was to make it part of the ordinary bt command. 

OK, if that's what the others want...

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-25 13:19       ` Eli Zaretskii
@ 2025-09-25 14:58         ` Simon Marchi
  2025-09-26  7:45           ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Simon Marchi @ 2025-09-25 14:58 UTC (permalink / raw)
  To: Eli Zaretskii, Schimpe, Christina; +Cc: gdb-patches, thiago.bauermann

On 9/25/25 9:19 AM, Eli Zaretskii wrote:
>> From: "Schimpe, Christina" <christina.schimpe@intel.com>
>> CC: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>, "Thiago Jung
>>  Bauermann" <thiago.bauermann@linaro.org>
>> Date: Thu, 25 Sep 2025 11:06:32 +0000
>>
>>>> (gdb) help bt shadow
>>>> Print backtrace of all shadow stack frames, or innermost COUNT frames.
>>>> Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
>>>
>>> Thanks, but I wonder if this UI is the best we can come up with.
>>> The "backtrace" command doesn't currently have non-option arguments
>>> except COUNT. 
>>
>> Hm, not sure if I fully understand what you mean by non-option arguments.
>> "bt shadow" does not introduce more non-option arguments, except if you say
>> that "shadow" itself is interpreted as "non-option" argument.
> 
> That's what I'm saying, yes.
> 
>>> We have just got rid of qualifier arguments like "full"
>>> and "hide".  Instead of introducing qualifier arguments anew, why not go the
>>> way of thread-related commands and add a new command "shadow", so the
>>> user could say "shadow backtrace" and maybe in the future also other sub-
>>> commands?
>>
>> This has been discussed in a thread years ago:
>> https://sourceware.org/pipermail/gdb/2023-December/051024.html
>> The direction was to make it part of the ordinary bt command. 
> 
> OK, if that's what the others want...

My initial reaction was: why not use a "-shadow" option to the backtrace
command, instead of introducing "backtrace shadow", which is effectively
a separate command?  I then re-read the thread above, and I saw that
"-shadow" was actually part of the options considered, and even seemed
like the preferred option of many.  I didn't see "backtrace shadow"
proposed there, so I'm confused.

The thing I don't like about "backtrace shadow" is: I don't like when a
command ("backtrace") is both a prefix and a regular command.  It can
paint us in a corner at some point.  Let's say we want to introduce the
syntax:

  (gdb) backtrace SYMBOL

at some point, then the sub-command "shadow" would conflict if someone
wanted to call the command "backtrace" with the symbol "shadow".

From what I read in the thread above, the downside of the "backtrace
-shadow" option is that some other options wouldn't make sense for the
shadow backtrace.  I don't think it's a problem, those other options can
just be ignored when printing the shadow stack.

Simon

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-25 14:58         ` Simon Marchi
@ 2025-09-26  7:45           ` Schimpe, Christina
  2025-10-29 15:05             ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-09-26  7:45 UTC (permalink / raw)
  To: Simon Marchi, Eli Zaretskii; +Cc: gdb-patches, thiago.bauermann

Hi Simon,

Thank you for the feedback. 

> -----Original Message-----
> From: Simon Marchi <simark@simark.ca>
> Sent: Thursday, September 25, 2025 4:59 PM
> To: Eli Zaretskii <eliz@gnu.org>; Schimpe, Christina
> <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> On 9/25/25 9:19 AM, Eli Zaretskii wrote:
> >> From: "Schimpe, Christina" <christina.schimpe@intel.com>
> >> CC: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>,
> >> "Thiago Jung  Bauermann" <thiago.bauermann@linaro.org>
> >> Date: Thu, 25 Sep 2025 11:06:32 +0000
> >>
> >>>> (gdb) help bt shadow
> >>>> Print backtrace of all shadow stack frames, or innermost COUNT frames.
> >>>> Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
> >>>
> >>> Thanks, but I wonder if this UI is the best we can come up with.
> >>> The "backtrace" command doesn't currently have non-option arguments
> >>> except COUNT.
> >>
> >> Hm, not sure if I fully understand what you mean by non-option
> arguments.
> >> "bt shadow" does not introduce more non-option arguments, except if
> >> you say that "shadow" itself is interpreted as "non-option" argument.
> >
> > That's what I'm saying, yes.
> >
> >>> We have just got rid of qualifier arguments like "full"
> >>> and "hide".  Instead of introducing qualifier arguments anew, why
> >>> not go the way of thread-related commands and add a new command
> >>> "shadow", so the user could say "shadow backtrace" and maybe in the
> >>> future also other sub- commands?
> >>
> >> This has been discussed in a thread years ago:
> >> https://sourceware.org/pipermail/gdb/2023-December/051024.html
> >> The direction was to make it part of the ordinary bt command.
> >
> > OK, if that's what the others want...
> 
> My initial reaction was: why not use a "-shadow" option to the backtrace
> command, instead of introducing "backtrace shadow", which is effectively a
> separate command?  I then re-read the thread above, and I saw that "-
> shadow" was actually part of the options considered, and even seemed like
> the preferred option of many.  I didn't see "backtrace shadow"
> proposed there, so I'm confused.

That's true. It has never been discussed.  When discussing this in the thread I
didn't have the option of subcommands in mind. 

> The thing I don't like about "backtrace shadow" is: I don't like when a
> command ("backtrace") is both a prefix and a regular command.  It can paint
> us in a corner at some point.  Let's say we want to introduce the
> syntax:
> 
>   (gdb) backtrace SYMBOL
> 
> at some point, then the sub-command "shadow" would conflict if someone
> wanted to call the command "backtrace" with the symbol "shadow".

Good point, I did not think about that.

> From what I read in the thread above, the downside of the "backtrace -
> shadow" option is that some other options wouldn't make sense for the
> shadow backtrace.  I don't think it's a problem, those other options can just be
> ignored when printing the shadow stack.

Yeah, this was one reason for me to not use "bt -shadow".
Another reason was that the current options of the ordinary backtrace command only
change the output format/range. The output of "bt shadow" looks similar in the first
place, but is created completely different so I also wanted to make a difference by
choosing the subcommand.

When moving to the implementation phase,  I decided to simply try it without reopening
the discussion in the gdb archives again. 
But if the common opinion here is to use "bt -shadow" I have no problem in changing that.
It should be straight forward to implement.

Christina
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c.
  2025-09-23 11:18 ` [PATCH 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
@ 2025-10-03 19:55   ` Tom Tromey
  0 siblings, 0 replies; 67+ messages in thread
From: Tom Tromey @ 2025-10-03 19:55 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

> To avoid code duplication, create a new function 'stack.c:find_symbol_funname',
> which will be used in 'stack.c:find_frame_funname' and
> 'stack.c:info_frame_command_core'.
> The function will also be used in a following commit.

Thanks.

> +  if (funname == nullptr)
> +    {
> +      /* If we didn't hit the C++ case above, set *funname here.  */
> +      funname.reset (xstrdup (print_name));

This should use make_unique_xstrdup and plain assignment.

> +	funname.reset (xstrdup (msymbol.minsym->print_name ()));

Here too.

Tom

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument.
  2025-09-23 11:18 ` [PATCH 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
@ 2025-10-03 19:56   ` Tom Tromey
  0 siblings, 0 replies; 67+ messages in thread
From: Tom Tromey @ 2025-10-03 19:56 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

Christina> The function will be used in a later patch "gdb: Implement 'bt shadow'
Christina> to print the shadow stack backtrace.".

Thanks, this is ok.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 5/9] gdb: Refactor 'stack.c:print_frame_info'.
  2025-09-23 11:18 ` [PATCH 5/9] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
@ 2025-10-03 20:03   ` Tom Tromey
  0 siblings, 0 replies; 67+ messages in thread
From: Tom Tromey @ 2025-10-03 20:03 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

Christina> This patch restructures the 'stack.c:print_frame_info' function by
Christina> introducing two new functions in stack.c.  The new functions
Christina> will also be called in a later patch "gdb: Implement 'bt shadow'
Christina> subcommand to print the shadow stack backtrace."

Looks good, thank you.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 2/9] gdb: Refactor 'stack.c:print_frame'.
  2025-09-23 11:18 ` [PATCH 2/9] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
@ 2025-10-03 20:05   ` Tom Tromey
  0 siblings, 0 replies; 67+ messages in thread
From: Tom Tromey @ 2025-10-03 20:05 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

Christina> Refactor the function 'stack.c:print_frame' by introducing several
Christina> small functions.
Christina> The functions will also be used in a later patch "gdb: Implement
Christina> 'bt shadow' to print the shadow stack backtrace."

Thanks.

Christina> +static void
Christina> +print_funname (ui_out *uiout,
Christina> +	       gdb::unique_xmalloc_ptr<char> const &funname)
Christina> +{
Christina> +  annotate_frame_function_name ();
Christina> +  string_file stb;
Christina> +  gdb_puts (funname ? funname.get () : "??", &stb);
Christina> +  uiout->field_stream ("func", stb, function_name_style.style ());

Not really your problem, but using string_file here seems like overkill.
This would be better as plain field_string using funname directly.

anyway,
Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-23 11:18 ` [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace Christina Schimpe
  2025-09-23 11:47   ` Eli Zaretskii
@ 2025-10-03 20:15   ` Tom Tromey
  2025-10-12 19:45     ` Schimpe, Christina
  2025-10-31  4:02   ` Thiago Jung Bauermann
  2 siblings, 1 reply; 67+ messages in thread
From: Tom Tromey @ 2025-10-03 20:15 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

Christina> Add a subcommand 'bt shadow' for the ordinary backtrace command which
Christina> prints the shadow stack backtrace.
Christina> Similar to the ordinary backtrace command 'bt shadow' can be configured
Christina> using COUNT and the command line option -frame-info.  However, we always
Christina> print the address and the command is not affected by the setting
Christina> "print address" as well as the setting "print frame-info location-and-address".
Christina> Also we do not print the frame arguments.

Christina> --- a/gdb/annotate.c
Christina> +++ b/gdb/annotate.c
[...]

FWIW annotations have been deprecated for ages.  Unless you have a real
need for them I would suggest simply dropping this part of the patch.

Tom

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2025-09-23 11:18 ` [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
  2025-09-23 11:53   ` Eli Zaretskii
@ 2025-10-03 20:17   ` Tom Tromey
  2025-10-12 19:54     ` Schimpe, Christina
  2025-11-26  4:26   ` Thiago Jung Bauermann
  2 siblings, 1 reply; 67+ messages in thread
From: Tom Tromey @ 2025-10-03 20:17 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

Christina> Add the mi command for the subcommand "backtrace shadow".
Christina> Similar to the mi interface for the ordinary backtrace command,
Christina> support low-frame and high-frame as command line parameters.

Christina> +      if (uiout->is_mi_like_p ())
Christina> +	{
Christina> +	  uiout->field_string
Christina> +	    ("arch", (gdbarch_bfd_arch_info (gdbarch))->printable_name);
Christina> +	}

I was wondering if the arch field is really useful to clients.

Tom

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2025-09-25 11:46 ` [PATCH 0/9] Add new command to print the shadow stack backtrace Schimpe, Christina
@ 2025-10-08  1:46   ` Thiago Jung Bauermann
  2025-10-13  1:18     ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-10-08  1:46 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

Hello Christina,

Sorry for the delay. I'm going over them now and will have some review
comments soon.

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

> I did not test the "bt shadow" command for ARM's GCS. Does the command make sense for GCS,
> too ?

Yes, it does. I think this is useful, thanks!

> If yes, does the implementation I shared here makes sense to you in general?

I'm still in the middle of reviewing it, but so far I think so.

> I tried to address the different Aarch and Intel behavior we discussed here
> https://sourceware.org/pipermail/gdb-patches/2025-June/218947.html 
> by introducing a new gdbarch hook top_addr_empty_shadow_stack.
>
> Does that go into the right direction for your purposes?

Yes, I believe so. Thank you for addressing this difference between the
architectures.

I tested your patches and there's a bug unfortunately:

FAIL: gdb.arch/aarch64-gcs-disp-step.exp: continue to breakpoint at blr
FAIL: gdb.arch/aarch64-gcs-disp-step.exp: continue until exit
FAIL: gdb.arch/aarch64-gcs.exp: call inferior function
FAIL: gdb.arch/aarch64-gcs-return.exp: test inferior call and continue: call (int) call2()
FAIL: gdb.arch/aarch64-gcs-return.exp: test inferior call and continue: continue until exit
FAIL: gdb.arch/aarch64-gcs-return.exp: test frame level update: check gcspr of frame 1
FAIL: gdb.arch/aarch64-gcs-return.exp: test frame level update: check gcspr of frame 2
FAIL: gdb.arch/aarch64-gcs-return.exp: test return from current frame: continue until exit

In each FAIL above a GCS error happens unexpectedly in the inferior. It
appears that GDB isn't updating the shadow stack correctly. I still have
to see what exactly is the problem.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-10-03 20:15   ` Tom Tromey
@ 2025-10-12 19:45     ` Schimpe, Christina
  2026-02-19 17:24       ` Tom Tromey
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-10-12 19:45 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

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

Hi Tom,

Thank you for the feedback. 
Please see my comment for annotations below.

> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Friday, October 3, 2025 10:15 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> >>>>> "Christina" == Christina Schimpe <christina.schimpe@intel.com> writes:
> 
> Christina> Add a subcommand 'bt shadow' for the ordinary backtrace
> Christina> command which prints the shadow stack backtrace.
> Christina> Similar to the ordinary backtrace command 'bt shadow' can be
> Christina> configured using COUNT and the command line option
> Christina> -frame-info.  However, we always print the address and the
> Christina> command is not affected by the setting "print address" as well as
> the setting "print frame-info location-and-address".
> Christina> Also we do not print the frame arguments.
> 
> Christina> --- a/gdb/annotate.c
> Christina> +++ b/gdb/annotate.c
> [...]
> 
> FWIW annotations have been deprecated for ages.  Unless you have a real
> need for them I would suggest simply dropping this part of the patch.

No, I don't have a real need for them. 
I implemented them as annotations are implemented for the ordinary bt command. 
Since "bt shadow" shares a couple of functions I wasn't sure. Partially having annotations
for "bt shadow" seemed wrong, too.

Alternatively, I see 2 options:
-  I could add a check and in case of bt shadow not print annotations at all
-  Remove annotations for the bt command completely

I only found this comment in the docs:
"The annotation mechanism has largely been superseded by GDB/MI (see GDB/MI)."
https://sourceware.org/gdb/current/onlinedocs/gdb.html/Annotations.html#Annotations
I am not sure if that means that they are deprecated or just rarely used. But maybe I am missing something.

What do you think?

Regards,
Christina

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

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

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2025-10-03 20:17   ` Tom Tromey
@ 2025-10-12 19:54     ` Schimpe, Christina
  2025-10-13  0:06       ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-10-12 19:54 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

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

> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Friday, October 3, 2025 10:18 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
> 
> >>>>> "Christina" == Christina Schimpe <christina.schimpe@intel.com> writes:
> 
> Christina> Add the mi command for the subcommand "backtrace shadow".
> Christina> Similar to the mi interface for the ordinary backtrace
> Christina> command, support low-frame and high-frame as command line
> parameters.
> 
> Christina> +      if (uiout->is_mi_like_p ())
> Christina> +	{
> Christina> +	  uiout->field_string
> Christina> +	    ("arch", (gdbarch_bfd_arch_info (gdbarch))-
> >printable_name);
> Christina> +	}
> 
> I was wondering if the arch field is really useful to clients.

The commit message of commit "MI: Print frame architecture when printing frames on an MI channel",
which added the field for -stack-list-frames, states:

"This is useful for MI clients that need to know the architecture in
order to perform further analysis, for example to use their own
disassembler to analyze machine code."

So I think we should have it for the mi command of "bt shadow" as well.

Regards,
Christina

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

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

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2025-10-12 19:54     ` Schimpe, Christina
@ 2025-10-13  0:06       ` Thiago Jung Bauermann
  0 siblings, 0 replies; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-10-13  0:06 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Tom Tromey, gdb-patches

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

>> -----Original Message-----
>> From: Tom Tromey <tom@tromey.com>
>> Sent: Friday, October 3, 2025 10:18 PM
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: gdb-patches@sourceware.org
>> Subject: Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
>> 
>> >>>>> "Christina" == Christina Schimpe <christina.schimpe@intel.com> writes:
>> 
>> Christina> Add the mi command for the subcommand "backtrace shadow".
>> Christina> Similar to the mi interface for the ordinary backtrace
>> Christina> command, support low-frame and high-frame as command line
>> parameters.
>> 
>> Christina> +      if (uiout->is_mi_like_p ())
>> Christina> +	{
>> Christina> +	  uiout->field_string
>> Christina> +	    ("arch", (gdbarch_bfd_arch_info (gdbarch))-
>> >printable_name);
>> Christina> +	}
>> 
>> I was wondering if the arch field is really useful to clients.
>
> The commit message of commit "MI: Print frame architecture when printing frames on an MI
> channel",
> which added the field for -stack-list-frames, states:
>
> "This is useful for MI clients that need to know the architecture in
> order to perform further analysis, for example to use their own
> disassembler to analyze machine code."
>
> So I think we should have it for the mi command of "bt shadow" as well.

The old PowerPC Cell processor was an heterogeneous system (PowerPC main
processor and SPU accelerators) and could have frames of both those
architectures in a single stack, so this field would be useful in that
case.

I think there are other targets where this can happen, but I can't
remember the details right now. Perhaps at least one of the GPU
backends?

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-25 11:06     ` Schimpe, Christina
  2025-09-25 13:19       ` Eli Zaretskii
@ 2025-10-13  1:17       ` Thiago Jung Bauermann
  2025-10-13  7:19         ` Schimpe, Christina
  1 sibling, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-10-13  1:17 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Eli Zaretskii, gdb-patches

Hello,

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

>> -----Original Message-----
>> From: Eli Zaretskii <eliz@gnu.org>
>> Sent: Tuesday, September 23, 2025 1:47 PM
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: gdb-patches@sourceware.org
>> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
>> stack backtrace.
>> 
>> > From: Christina Schimpe <christina.schimpe@intel.com>
>> > Date: Tue, 23 Sep 2025 11:18:39 +0000
>> >
>> > Add a subcommand 'bt shadow' for the ordinary backtrace command which
>> > prints the shadow stack backtrace.
>> > Similar to the ordinary backtrace command 'bt shadow' can be
>> > configured using COUNT and the command line option -frame-info.
>> > However, we always print the address and the command is not affected
>> > by the setting "print address" as well as the setting "print frame-info
>> location-and-address".
>> > Also we do not print the frame arguments.
>> >
>> > Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]
>> >
>> > Help output:
>> > ~~
>> > (gdb) help bt shadow
>> > Print backtrace of all shadow stack frames, or innermost COUNT frames.
>> > Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
>> 
>> We have just got rid of qualifier arguments like "full"
>> and "hide".  Instead of introducing qualifier arguments anew, why not go the
>> way of thread-related commands and add a new command "shadow", so the
>> user could say "shadow backtrace" and maybe in the future also other sub-
>> commands?
>
> This has been discussed in a thread years ago:
> https://sourceware.org/pipermail/gdb/2023-December/051024.html
> The direction was to make it part of the ordinary bt command. 
>
> Currently, we don't plan any further commands for the shadow stack. But
> maybe for ARM's Guarded Control Stack any further options are planned?
> I added Thiago in cc here. Maybe he has some more input.

Yes, I plan to add two commands for AArch64 Guarded Control Stack, as
mentioned in that thread:

https://inbox.sourceware.org/gdb/87h6kbfazh.fsf@linaro.org/

In that email, I suggested putting them under "info shadow-stack", but
later the people in the thread considered that just adding a top-level
"shadow-stack" command would be better.

I do think that "backtrace -shadow" is a good option, so IMO we could
have both it and the "shadow-stack" umbrella command. It could even have
"shadow-stack backtrace" as an alias to "backtrace -shadow" to improve
discoverability.

You mentioned that x86 doesn't need the "enabled" and "locked" commands
because for that architecture the user can look in /proc/$PID/status,
but with those commands I also plan to allow the user to change the
enabled and locked GCS features. Wouldn't that be useful for x86?

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2025-10-08  1:46   ` Thiago Jung Bauermann
@ 2025-10-13  1:18     ` Thiago Jung Bauermann
  2025-10-13  6:34       ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-10-13  1:18 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:

> I tested your patches and there's a bug unfortunately:
>
> FAIL: gdb.arch/aarch64-gcs-disp-step.exp: continue to breakpoint at blr
> FAIL: gdb.arch/aarch64-gcs-disp-step.exp: continue until exit
> FAIL: gdb.arch/aarch64-gcs.exp: call inferior function
> FAIL: gdb.arch/aarch64-gcs-return.exp: test inferior call and continue: call (int) call2()
> FAIL: gdb.arch/aarch64-gcs-return.exp: test inferior call and continue: continue until
> exit
> FAIL: gdb.arch/aarch64-gcs-return.exp: test frame level update: check gcspr of frame 1
> FAIL: gdb.arch/aarch64-gcs-return.exp: test frame level update: check gcspr of frame 2
> FAIL: gdb.arch/aarch64-gcs-return.exp: test return from current frame: continue until exit
>
> In each FAIL above a GCS error happens unexpectedly in the inferior. It
> appears that GDB isn't updating the shadow stack correctly. I still have
> to see what exactly is the problem.

The problem turned out to be simple. I just had to set the regnum for
the GCSPR in the gdbarch:

diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 95af82c26327..9e866fc319d4 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -4780,6 +4780,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);
 
+  if (tdep->has_gcs ())
+    /* AArch64's shadow stack pointer is the GCSPR.  */
+    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
+
   /* ABI */
   set_gdbarch_short_bit (gdbarch, 16);
   set_gdbarch_int_bit (gdbarch, 32);

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2025-10-13  1:18     ` Thiago Jung Bauermann
@ 2025-10-13  6:34       ` Schimpe, Christina
  2025-10-29 14:52         ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-10-13  6:34 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Monday, October 13, 2025 3:19 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 0/9] Add new command to print the shadow stack
> backtrace
> 
> Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:
> 
> > I tested your patches and there's a bug unfortunately:
> >
> > FAIL: gdb.arch/aarch64-gcs-disp-step.exp: continue to breakpoint at
> > blr
> > FAIL: gdb.arch/aarch64-gcs-disp-step.exp: continue until exit
> > FAIL: gdb.arch/aarch64-gcs.exp: call inferior function
> > FAIL: gdb.arch/aarch64-gcs-return.exp: test inferior call and
> > continue: call (int) call2()
> > FAIL: gdb.arch/aarch64-gcs-return.exp: test inferior call and
> > continue: continue until exit
> > FAIL: gdb.arch/aarch64-gcs-return.exp: test frame level update: check
> > gcspr of frame 1
> > FAIL: gdb.arch/aarch64-gcs-return.exp: test frame level update: check
> > gcspr of frame 2
> > FAIL: gdb.arch/aarch64-gcs-return.exp: test return from current frame:
> > continue until exit
> >
> > In each FAIL above a GCS error happens unexpectedly in the inferior.
> > It appears that GDB isn't updating the shadow stack correctly. I still
> > have to see what exactly is the problem.
> 
> The problem turned out to be simple. I just had to set the regnum for the
> GCSPR in the gdbarch:
> 
> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> 95af82c26327..9e866fc319d4 100644
> --- a/gdb/aarch64-tdep.c
> +++ b/gdb/aarch64-tdep.c
> @@ -4780,6 +4780,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);
> 
> +  if (tdep->has_gcs ())
> +    /* AArch64's shadow stack pointer is the GCSPR.  */
> +    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
> +
>    /* ABI */
>    set_gdbarch_short_bit (gdbarch, 16);
>    set_gdbarch_int_bit (gdbarch, 32);
> 
> --
> Thiago

Ah, I forgot about that. Cool that it works now. 😊

Thanks,
Christina

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-10-13  1:17       ` Thiago Jung Bauermann
@ 2025-10-13  7:19         ` Schimpe, Christina
  2025-10-31  4:39           ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-10-13  7:19 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: Eli Zaretskii, gdb-patches

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

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Monday, October 13, 2025 3:17 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: Eli Zaretskii <eliz@gnu.org>; gdb-patches@sourceware.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> Hello,
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> >> -----Original Message-----
> >> From: Eli Zaretskii <eliz@gnu.org>
> >> Sent: Tuesday, September 23, 2025 1:47 PM
> >> To: Schimpe, Christina <christina.schimpe@intel.com>
> >> Cc: gdb-patches@sourceware.org
> >> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the
> >> shadow stack backtrace.
> >>
> >> > From: Christina Schimpe <christina.schimpe@intel.com>
> >> > Date: Tue, 23 Sep 2025 11:18:39 +0000
> >> >
> >> > Add a subcommand 'bt shadow' for the ordinary backtrace command
> >> > which prints the shadow stack backtrace.
> >> > Similar to the ordinary backtrace command 'bt shadow' can be
> >> > configured using COUNT and the command line option -frame-info.
> >> > However, we always print the address and the command is not
> >> > affected by the setting "print address" as well as the setting
> >> > "print frame-info
> >> location-and-address".
> >> > Also we do not print the frame arguments.
> >> >
> >> > Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]
> >> >
> >> > Help output:
> >> > ~~
> >> > (gdb) help bt shadow
> >> > Print backtrace of all shadow stack frames, or innermost COUNT frames.
> >> > Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
> >>
> >> We have just got rid of qualifier arguments like "full"
> >> and "hide".  Instead of introducing qualifier arguments anew, why not
> >> go the way of thread-related commands and add a new command
> "shadow",
> >> so the user could say "shadow backtrace" and maybe in the future also
> >> other sub- commands?
> >
> > This has been discussed in a thread years ago:
> > https://sourceware.org/pipermail/gdb/2023-December/051024.html
> > The direction was to make it part of the ordinary bt command.
> >
> > Currently, we don't plan any further commands for the shadow stack.
> > But maybe for ARM's Guarded Control Stack any further options are
> planned?
> > I added Thiago in cc here. Maybe he has some more input.
> 
> Yes, I plan to add two commands for AArch64 Guarded Control Stack, as
> mentioned in that thread:
> 
> https://inbox.sourceware.org/gdb/87h6kbfazh.fsf@linaro.org/
> 
> In that email, I suggested putting them under "info shadow-stack", but later
> the people in the thread considered that just adding a top-level "shadow-
> stack" command would be better.
> 
> I do think that "backtrace -shadow" is a good option, so IMO we could have
> both it and the "shadow-stack" umbrella command. It could even have
> "shadow-stack backtrace" as an alias to "backtrace -shadow" to improve
> discoverability.

I have nothing against that I think, are there similar commands in GDB that have such an alias feature?

> You mentioned that x86 doesn't need the "enabled" and "locked" commands
> because for that architecture the user can look in /proc/$PID/status, but with
> those commands I also plan to allow the user to change the enabled and
> locked GCS features. Wouldn't that be useful for x86?

For x86 the user can configure the shadow stack enabled and locked state via arch_prctls:
https://docs.kernel.org/next/x86/shstk.html

Is that possible for GCS, too?
I am not sure if we can easily change it using GDB. We currently only read/write the shadow stack pointer via ptrace.

But it is possible to see those states by running "info proc status" in gdb:
[...]
x86_Thread_features:	shstk 
x86_Thread_features_locked:	shstk wrss

So I'd say we don't really need it, but it might be useful to improve discoverability, too.

I think a similar topic is the GDB GCS feature to find out the reason for a segmentation fault:
~~~
Program received signal SIGSEGV, Segmentation fault
Guarded Control Stack error.
~~~
In theory, the user can use the si code by examining siginfo, but having the additional line as for GCS
improves discoverability.

So for now we decided to keep it simple, and hope that the user will get along with the existing interfaces.
If there is the common opinion to add the commands (read-only) and an additional line for SEGVs due to a
shadow-stack/cet violation in case of x86, I think we can add it, too.

Regards,
Christina



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

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

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2025-10-13  6:34       ` Schimpe, Christina
@ 2025-10-29 14:52         ` Schimpe, Christina
  2025-10-31  0:47           ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-10-29 14:52 UTC (permalink / raw)
  To: 'Thiago Jung Bauermann'; +Cc: 'gdb-patches@sourceware.org'

> -----Original Message-----
> From: Schimpe, Christina
> Sent: Monday, October 13, 2025 8:35 AM
> To: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Cc: gdb-patches@sourceware.org
> Subject: RE: [PATCH 0/9] Add new command to print the shadow stack
> backtrace
> 
> > -----Original Message-----
> > From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> > Sent: Monday, October 13, 2025 3:19 AM
> > To: Schimpe, Christina <christina.schimpe@intel.com>
> > Cc: gdb-patches@sourceware.org
> > Subject: Re: [PATCH 0/9] Add new command to print the shadow stack
> > backtrace
> >
> > Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:
> >
> > > I tested your patches and there's a bug unfortunately:
> > >
> > > FAIL: gdb.arch/aarch64-gcs-disp-step.exp: continue to breakpoint at
> > > blr
> > > FAIL: gdb.arch/aarch64-gcs-disp-step.exp: continue until exit
> > > FAIL: gdb.arch/aarch64-gcs.exp: call inferior function
> > > FAIL: gdb.arch/aarch64-gcs-return.exp: test inferior call and
> > > continue: call (int) call2()
> > > FAIL: gdb.arch/aarch64-gcs-return.exp: test inferior call and
> > > continue: continue until exit
> > > FAIL: gdb.arch/aarch64-gcs-return.exp: test frame level update:
> > > check gcspr of frame 1
> > > FAIL: gdb.arch/aarch64-gcs-return.exp: test frame level update:
> > > check gcspr of frame 2
> > > FAIL: gdb.arch/aarch64-gcs-return.exp: test return from current frame:
> > > continue until exit
> > >
> > > In each FAIL above a GCS error happens unexpectedly in the inferior.
> > > It appears that GDB isn't updating the shadow stack correctly. I
> > > still have to see what exactly is the problem.
> >
> > The problem turned out to be simple. I just had to set the regnum for
> > the GCSPR in the gdbarch:
> >
> > diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> > 95af82c26327..9e866fc319d4 100644
> > --- a/gdb/aarch64-tdep.c
> > +++ b/gdb/aarch64-tdep.c
> > @@ -4780,6 +4780,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);
> >
> > +  if (tdep->has_gcs ())
> > +    /* AArch64's shadow stack pointer is the GCSPR.  */
> > +    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
> > +
> >    /* ABI */
> >    set_gdbarch_short_bit (gdbarch, 16);
> >    set_gdbarch_int_bit (gdbarch, 32);
> >
> > --
> > Thiago
> 
> Ah, I forgot about that. Cool that it works now. 😊
> 
> Thanks,
> Christina

Hi Thiago,

I thought about this again. In addition to setting the regnum for the shadow stack pointer,
I would have expected that you also have to implement the gdbarch hook
top_addr_empty_shadow_stack to make GCS work for shadow stack pointer unwinding
and the shadow stack backtrace, or am I missing something?

Both, the aarch64 implememtation for top_addr_empty_shadow_stack and ssp regnum
configuration, should then be part the first patch "Generalize handling ...".
Does that make sense to you?

Also I should probably add proper error messages when the configuration of ssp_regnum is missing,
and improve the documentation for required gdbarch hooks to properly implement "bt shadow" in
gdbarch_components.py.

Kind Regards,
Christina




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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-26  7:45           ` Schimpe, Christina
@ 2025-10-29 15:05             ` Schimpe, Christina
  2025-10-29 15:28               ` Guinevere Larsen
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-10-29 15:05 UTC (permalink / raw)
  To: 'Simon Marchi', 'Eli Zaretskii'
  Cc: 'gdb-patches@sourceware.org',
	'thiago.bauermann@linaro.org'

> -----Original Message-----
> From: Schimpe, Christina
> Sent: Friday, September 26, 2025 9:45 AM
> To: 'Simon Marchi' <simark@simark.ca>; Eli Zaretskii <eliz@gnu.org>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> Hi Simon,
> 
> Thank you for the feedback.
> 
> > -----Original Message-----
> > From: Simon Marchi <simark@simark.ca>
> > Sent: Thursday, September 25, 2025 4:59 PM
> > To: Eli Zaretskii <eliz@gnu.org>; Schimpe, Christina
> > <christina.schimpe@intel.com>
> > Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> > Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the
> > shadow stack backtrace.
> >
> > On 9/25/25 9:19 AM, Eli Zaretskii wrote:
> > >> From: "Schimpe, Christina" <christina.schimpe@intel.com>
> > >> CC: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>,
> > >> "Thiago Jung  Bauermann" <thiago.bauermann@linaro.org>
> > >> Date: Thu, 25 Sep 2025 11:06:32 +0000
> > >>
> > >>>> (gdb) help bt shadow
> > >>>> Print backtrace of all shadow stack frames, or innermost COUNT
> frames.
> > >>>> Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
> > >>>
> > >>> Thanks, but I wonder if this UI is the best we can come up with.
> > >>> The "backtrace" command doesn't currently have non-option
> > >>> arguments except COUNT.
> > >>
> > >> Hm, not sure if I fully understand what you mean by non-option
> > arguments.
> > >> "bt shadow" does not introduce more non-option arguments, except if
> > >> you say that "shadow" itself is interpreted as "non-option" argument.
> > >
> > > That's what I'm saying, yes.
> > >
> > >>> We have just got rid of qualifier arguments like "full"
> > >>> and "hide".  Instead of introducing qualifier arguments anew, why
> > >>> not go the way of thread-related commands and add a new command
> > >>> "shadow", so the user could say "shadow backtrace" and maybe in
> > >>> the future also other sub- commands?
> > >>
> > >> This has been discussed in a thread years ago:
> > >> https://sourceware.org/pipermail/gdb/2023-December/051024.html
> > >> The direction was to make it part of the ordinary bt command.
> > >
> > > OK, if that's what the others want...
> >
> > My initial reaction was: why not use a "-shadow" option to the
> > backtrace command, instead of introducing "backtrace shadow", which is
> > effectively a separate command?  I then re-read the thread above, and
> > I saw that "- shadow" was actually part of the options considered, and
> > even seemed like the preferred option of many.  I didn't see "backtrace
> shadow"
> > proposed there, so I'm confused.
> 
> That's true. It has never been discussed.  When discussing this in the thread
> I didn't have the option of subcommands in mind.
> 
> > The thing I don't like about "backtrace shadow" is: I don't like when
> > a command ("backtrace") is both a prefix and a regular command.  It
> > can paint us in a corner at some point.  Let's say we want to
> > introduce the
> > syntax:
> >
> >   (gdb) backtrace SYMBOL
> >
> > at some point, then the sub-command "shadow" would conflict if
> someone
> > wanted to call the command "backtrace" with the symbol "shadow".
> 
> Good point, I did not think about that.
> 
> > From what I read in the thread above, the downside of the "backtrace -
> > shadow" option is that some other options wouldn't make sense for the
> > shadow backtrace.  I don't think it's a problem, those other options
> > can just be ignored when printing the shadow stack.
> 
> Yeah, this was one reason for me to not use "bt -shadow".
> Another reason was that the current options of the ordinary backtrace
> command only change the output format/range. The output of "bt shadow"
> looks similar in the first place, but is created completely different so I also
> wanted to make a difference by choosing the subcommand.
> 
> When moving to the implementation phase,  I decided to simply try it
> without reopening the discussion in the gdb archives again.
> But if the common opinion here is to use "bt -shadow" I have no problem in
> changing that.
> It should be straight forward to implement.

Kindly pinging for feedback on this discussion to clarify if we should better use
- "bt -shadow" (command line option) 
- "bt shadow" (subcommand approach, current implementation).

My personal opinion is still to use the subcommand approach, but, as I already said,
I think "bt -shadow" would be fine, too.

Please also see this discussion with Thiago:
https://sourceware.org/pipermail/gdb-patches/2025-October/221660.html

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-10-29 15:05             ` Schimpe, Christina
@ 2025-10-29 15:28               ` Guinevere Larsen
  2025-11-03 19:47                 ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Guinevere Larsen @ 2025-10-29 15:28 UTC (permalink / raw)
  To: Schimpe, Christina, 'Simon Marchi', 'Eli Zaretskii'
  Cc: 'gdb-patches@sourceware.org',
	'thiago.bauermann@linaro.org'

On 10/29/25 12:05 PM, Schimpe, Christina wrote:
> Kindly pinging for feedback on this discussion to clarify if we should better use
> - "bt -shadow" (command line option)
> - "bt shadow" (subcommand approach, current implementation).
>
> My personal opinion is still to use the subcommand approach, but, as I already said,
> I think "bt -shadow" would be fine, too.
>
> Please also see this discussion with Thiago:
> https://sourceware.org/pipermail/gdb-patches/2025-October/221660.html

If it makes any difference, I personally prefer the command line option 
approach. That's based on looking at how record, record full and record 
btrace works, it is much easier to understand the code if the decision 
is made on the command's function itself

-- 
Cheers,
Guinevere Larsen
It/she


^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2025-10-29 14:52         ` Schimpe, Christina
@ 2025-10-31  0:47           ` Thiago Jung Bauermann
  2025-12-30 10:16             ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-10-31  0:47 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: 'gdb-patches@sourceware.org'

Hello Christina,

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

>> -----Original Message-----
>> From: Schimpe, Christina
>> Sent: Monday, October 13, 2025 8:35 AM
>> To: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Cc: gdb-patches@sourceware.org
>> Subject: RE: [PATCH 0/9] Add new command to print the shadow stack
>> backtrace
>> 
>> > -----Original Message-----
>> > From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> > Sent: Monday, October 13, 2025 3:19 AM
>> > To: Schimpe, Christina <christina.schimpe@intel.com>
>> > Cc: gdb-patches@sourceware.org
>> > Subject: Re: [PATCH 0/9] Add new command to print the shadow stack
>> > backtrace
>> >
>> > Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:
>> >
>> > > I tested your patches and there's a bug unfortunately:
>> > The problem turned out to be simple. I just had to set the regnum for
>> > the GCSPR in the gdbarch:
>> >
>> > diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
>> > 95af82c26327..9e866fc319d4 100644
>> > --- a/gdb/aarch64-tdep.c
>> > +++ b/gdb/aarch64-tdep.c
>> > @@ -4780,6 +4780,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);
>> >
>> > +  if (tdep->has_gcs ())
>> > +    /* AArch64's shadow stack pointer is the GCSPR.  */
>> > +    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
>> > +
>> >    /* ABI */
>> >    set_gdbarch_short_bit (gdbarch, 16);
>> >    set_gdbarch_int_bit (gdbarch, 32);
>> 
>> Ah, I forgot about that. Cool that it works now. 😊
>
> I thought about this again. In addition to setting the regnum for the shadow stack pointer,
> I would have expected that you also have to implement the gdbarch hook
> top_addr_empty_shadow_stack to make GCS work for shadow stack pointer unwinding
> and the shadow stack backtrace, or am I missing something?

Ah, I have a comment about this on my review of patch 1 that is sitting
in my drafts folder. I was waiting to send them all together, but it's
been taking me longer than I expected. I will just send what I have so
far.

But essentially, if there's no gdbarch_top_addr_empty_shadow_stack hook
this patch series changes the check of whether ssp is empty from a <=
comparison with the end of the range to a < comparison. Because of this,
AArch64 doesn't need to implement the hook to make the existing
aarch64-gcs*.exp tests work. It still needs it to make the "bt shadow"
command work though.

> Both, the aarch64 implememtation for top_addr_empty_shadow_stack and ssp regnum
> configuration, should then be part the first patch "Generalize handling ...".
> Does that make sense to you?

Yes, patch 1 should include the changes needed to make the existing
aarch64-gcs*.exp tests passing. As of v1, that's just the ssp regnum
configuration. Depending on what you decide to do for v2, that could
include the top_addr_empty_shadow_stack hook as well.

> Also I should probably add proper error messages when the configuration of ssp_regnum is missing,

Is that possible? How would GDB distinguish between missing ssp regnum
configuration versus the target not supporting shadow stacks?

> and improve the documentation for required gdbarch hooks to properly implement "bt shadow" in
> gdbarch_components.py.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer.
  2025-09-23 11:18 ` [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
@ 2025-10-31  1:31   ` Thiago Jung Bauermann
  2025-11-17 11:18     ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-10-31  1:31 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

> 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 commit a subcommmand "backtrace shadow" of the ordinary

Too mmany m's in "subcommand".

> backtrace command will be added 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.

<snip>

> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
> index 500ac77d75a..95af82c2632 100644
> --- a/gdb/aarch64-tdep.c
> +++ b/gdb/aarch64-tdep.c

As mentioned before, this change is also needed in aarch64-tdep.c to
make this patch series work for AArch64:

diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 95af82c26327..9e866fc319d4 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -4780,6 +4780,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);
 
+  if (tdep->has_gcs ())
+    /* AArch64's shadow stack pointer is the GCSPR.  */
+    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
+
   /* ABI */
   set_gdbarch_short_bit (gdbarch, 16);
   set_gdbarch_int_bit (gdbarch, 32);

> -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)

IIUC, the '<=' comparison above isn't preserved by this patch. This
function is replaced by dwarf2_prev_ssp, which uses
gdbarch_address_in_shadow_stack_memory_range for this if condition,
whose comparison in find_addr_mem_range is:

      bool addr_in_mem_range
        = (addr >= map.start_address && addr < map.end_address);

Is this intended?

> -	    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;
> -}

<snip>

> diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
> new file mode 100644
> index 00000000000..d153d5fc846
> --- /dev/null
> +++ b/gdb/shadow-stack.c
> @@ -0,0 +1,167 @@
> +/* Manage a shadow stack pointer for GDB, the GNU debugger.
> +
> +   Copyright (C) 2024-2025 Free Software Foundation, Inc.

Should this really start at 2024? According to Andrew Burgess¹:

> The start date should be when the patches were first posted to the list,
> or otherwise made publicly available (e.g. Intel specific GDB release?).
> The end date should be updated to 2025.

Same question for other files created by this patch series.

> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include "defs.h"
> +#include "arch-utils.h"
> +#include "gdbcore.h"
> +#include "extract-store-integer.h"
> +#include "frame.h"
> +#include "frame-unwind.h"
> +#include "shadow-stack.h"
> +
> +enum class ssp_update_direction
> +{
> +  /* Update ssp towards the bottom of the shadow stack.  */
> +  bottom = 0,
> +
> +  /* Update ssp towards the top of the shadow stack.  */
> +  top
> +};

I find the bottom/top nomenclature confusing, especially because it's
supposed to mean the same thing whether the stack grows up or down. In
my mind, if the stack grow down then top means "oldest element", but if
the stack grows up, then top means "newest element".

But in this patch it seems that top means "newest element" regardless of
the direction of stack growth.

I would suggest changing the enum names above to something that's not
related to the vertical axis, so that their meaning will be clear
regardless of which direction the stack grows. A few suggestions:
shrink/grow, older/younger, outer/inner.

> +/* Return a new shadow stack pointer which is incremented or decremented
> +   by COUNT elements 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::bottom)
> +		      : (direction == ssp_update_direction::top);

All the parentheses above are superfluous and can be removed.

> +  CORE_ADDR new_ssp;

This variable is unused and can be removed. Because by default GDB build
with -Werror, this patch breaks the build with an "unused variable"
error until patch 6 which removes this variable.

> +  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 (gdbarch *gdbarch, regcache *regcache,

There's no need for a gdbarch argument. You can get it from the
regcache.

> +			const CORE_ADDR new_addr)
> +{
> +  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::top);
> +
> +  /* If NEW_SSP does not point to shadow stack memory, we assume the stack
> +     is full.  */
> +  std::pair<CORE_ADDR, CORE_ADDR> range;
> +  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> +						     new_ssp,
> +						     &range))

Range isn't really needed by this function. I suggest changing
gdbarch_address_in_shadow_stack_memory_range to allow for it to be
nullptr and then pass nullptr here.

Also, the line above fits in 80 columns and doesn't need to be broken,
even if "&range" is changed to "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 8 bytes to
> +     include the shadow stack token.  */

s/8 bytes/element size/

> +  const int element_size
> +    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
> +
> +  const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +
> +  write_memory_unsigned_integer (new_ssp, element_size, byte_order,
> +				 (ULONGEST) new_addr);
> +
> +  regcache_raw_write_unsigned (regcache,
> +			       gdbarch_ssp_regnum (gdbarch),
> +			       new_ssp);

The line above fits in 80 columns and doesn't need to be broken.

> +}
> +
> +/* 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))

This line fits in 80 columns and doesn't need to be broken.

> +	{
> +	  /* 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::bottom);
> +
> +	  /* On x86, if NEW_SSP points to the end of RANGE, it indicates
> +	     that NEW_SSP is valid, but the shadow stack is empty.  In
> +	     contrast, on ARM's Guarded Control Stack, if NEW_SSP points
> +	     to the end of RANGE, it means that the shadow stack pointer
> +	     is invalid.  */
> +	  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);
> +
> +	  /* Check whether the new SSP is valid.  Depending on the
> +	     architecture, this may rely on both
> +	     IS_TOP_ADDR_EMPTY_SHADOW_STACK and the return value of
> +	     gdbarch_address_in_shadow_stack_memory_range, or on the
> +	     latter only.  */
> +	  if (is_top_addr_empty_shadow_stack

Considering that, as previously mentioned,
gdbarch_address_in_shadow_stack_memory_range uses '<' rather than '<='
in the memory range check, AArch64 doesn't need
gdbarch_top_addr_empty_shadow_stack.

The way this if condition is written, I think it's x86_64 that would
need to provide an gdbarch_top_addr_empty_shadow_stack implementation.
I'm surprised it doesn't.

> +	      || gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> +							       ssp,

Shouldn't this argument be 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;
> +

Spurious empty line added here.

> +}
> diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
> new file mode 100644
> index 00000000000..5c3ba80974e
> --- /dev/null
> +++ b/gdb/shadow-stack.h
> @@ -0,0 +1,39 @@
> +/* Definitions to manage a shadow stack pointer for GDB, the GNU debugger.
> +
> +   Copyright (C) 2024-2025 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#ifndef GDB_SHADOW_STACK_H
> +#define GDB_SHADOW_STACK_H
> +
> +/* If shadow stack is enabled, push the address NEW_ADDR on the shadow
> +   stack and update the shadow stack pointer accordingly.  */
> +
> +void shadow_stack_push (gdbarch *gdbarch, regcache *regcache,

Recently, the project has been trying to make the header files contain
all the headers and definitions that they need, for the benefit of IDE and
language server users, so that these tools don't emit spurious errors
when showing a header file.

In this case, clangd is complaining here that regcache is
unknown. There's no need to include regcache.h just for it, just adding
a "class regcache" forward declaration at the beginning of the file is
enough.

> +			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,

The space after "value *" should be removed.

> +			 void **this_cache, int regnum);
> +
> +#endif /* GDB_SHADOW_STACK_H */

-- 
Thiago

¹ https://inbox.sourceware.org/gdb-patches/87ldo6c84t.fsf@redhat.com/

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-09-23 11:18 ` [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace Christina Schimpe
  2025-09-23 11:47   ` Eli Zaretskii
  2025-10-03 20:15   ` Tom Tromey
@ 2025-10-31  4:02   ` Thiago Jung Bauermann
  2025-11-17 20:14     ` Schimpe, Christina
  2 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-10-31  4:02 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

> Add a subcommand 'bt shadow' for the ordinary backtrace command which
> prints the shadow stack backtrace.
> Similar to the ordinary backtrace command 'bt shadow' can be configured
> using COUNT and the command line option -frame-info.  However, we always
> print the address and the command is not affected by the setting
> "print address" as well as the setting "print frame-info location-and-address".
> Also we do not print the frame arguments.
>
> Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]
>
> Help output:
> ~~
> (gdb) help bt shadow
> Print backtrace of all shadow stack frames, or innermost COUNT frames.
> Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
>
> Options:
>   -frame-info auto|source-line|location|source-and-location|location-and-address|short-location
>     Set printing of shadow stack frame information.
>
> With a negative COUNT, print outermost -COUNT elements.
> ~~

There's another thread discussion whether to use "bt -shadow" instead,
and/or "shadow-stack backtrace" so I won't comment on this here.

> Example for the output of 'bt shadow' on amd64 linux:
> ~~
> (gdb) bt shadow
> /#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
> /#1  0x000000000040112f in main at amd64-shadow-stack.c:21
> /#2  0x00007ffff7c3fe70 in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
> /#3  0x00007ffff7c3ff20 in __libc_start_main_impl at ../csu/libc-start.c:128
> /#4  0x0000000000401045 in _start
> ~~

Here's an example output in my aarch64-linux test VM:

(gdb) bt shadow
#0  0x0000aaaaaaaa08ac in call2 at aarch64-gcs-return.c:65
#1  0x0000aaaaaaaa08c0 in call1 at aarch64-gcs-return.c:71
#2  0x0000aaaaaaaa09d4 in main at aarch64-gcs-return.c:110
#3  0x0000000000000000 in ??

And here is the regular backtrace at the same point:

(gdb) bt
#0  call3 () at aarch64-gcs-return.c:58
#1  0x0000aaaaaaaa08ac in call2 () at aarch64-gcs-return.c:64
#2  0x0000aaaaaaaa08c0 in call1 () at aarch64-gcs-return.c:70
#3  0x0000aaaaaaaa09d4 in main () at aarch64-gcs-return.c:107

I have a few comments on this output, at least as it is appears for
GCS. I'm assuming x86 shadow stack output is similar:

1. The first thing that I notice is that the number of the frames don't
match between the regular stack and the shadow stack: frame 0 in the
regular stack doesn't exist in the shadow stack, regular frame 1 is
shadow frame 0, and so on.

I think this is confusing. It makes sense that frame 0 doesn't appear in
the shadow stack backtrace because there really isn't an entry for it in
the shadow stack, but I think the shadow entries should start with
number 1, to be consistent.

2. The line numbers corresponding to the return addresses don't match
between the regular backtrace and the shadow backtrace, even though the
return addresses are the same. I would say this is a bug. Perhaps there
can be a test making sure they match, if it's not too complicated?

3. One difference between my GCS test programs and your x86 test
programs is the point at which the shadow stack is enabled. In your
case, it appears to be done by glibc or the dynamic loader, very early
in the process execution. For the AArch64 testcases I'm doing it
explicitly in main itself, so that the test programs don't have to
depend on a supported glibc or loader. All this to say that there is no
GCS entry before main, yet one appears in the output above with a zeroed
return address. I think this can be solved by stopping the backtrace
loop if gdbarch_top_addr_empty_shadow_stack returns true in
backtrace_shadow_command. I have a suggestion about this further down.

> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index e8515883820..ebda4546b58 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -8840,6 +8840,53 @@ Display an absolute filename.
>  Show the current way to display filenames.
>  @end table
>  
> +@subsection Shadow stack backtrace
> +@value{GDBN} provides a subcommand of the backtrace command to print the
> +shadow stack backtrace.
> +
> +@anchor{shadowstack-backtrace-command}
> +@kindex backtrace shadow
> +@kindex bt shadow @r{(@code{backtrace shadow})}
> +To print a backtrace of the entire shadow stack, use the
> +@code{backtrace shadow} command, or its alias @code{bt shadow}.  This
> +command will print 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 (@xref{CET}) on x86.

Can you also add:

... and the Guarded Control Stack feature (@xref{GCS}) on AArch64.

which would also require a @node entry here:

@@ -27112,6 +27113,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}.
 
+@node GCS
 @subsubsection AArch64 Guarded Control Stack
 @cindex Guarded Control Stack, AArch64
 @cindex GCS, AArch64

<snip>

> +/* 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_frame_sal).  Otherwise, we would have some PC range if the

In the case of stack.c:frame_show_address, SAL comes from
find_frame_sal, but in this case it comes from find_pc_line, right?
Does the comment still apply in this case?

> +     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;
> +}
> +
> +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
> +    (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range);
> +
> +  /* The shadow stack pointer.  */
> +  CORE_ADDR ssp;

Please add an empty line between each member variable and the next
variable's documentation comment.

> +  /* The value of the shadow stack at SSP.  */
> +  CORE_ADDR value;
> +  /* The level of the element on the shadow stack.  */
> +  unsigned long level;
> +  /* 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;
> +};
> +
> +static
> +gdb::unique_xmalloc_ptr<char> find_pc_funname (CORE_ADDR pc)

The return type should be together with the static.
Also, this function needs a documentation comment.

> +{
> +  symbol *func = find_pc_function (pc);
> +  if (func)
> +    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.reset (xstrdup (msymbol.minsym->print_name ()));

Just repeating Tom's comment from patch 4:

This should use make_unique_xstrdup and plain assignment.

> +
> +  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, gdbarch *gdbarch,
> +   const shadow_stack_print_options &print_options,
> +   const shadow_stack_frame_info &frame, print_what print_what)
> +{
> +  if (print_options.print_frame_info != print_frame_info_auto)
> +    {
> +      /* Use the specific frame information desired by the user.  */
> +      print_what
> +	= *print_frame_info_to_print_what (print_options.print_frame_info);
> +    }
> +
> +  /* In contrast to find_frame_sal which is used for the ordinary backtrace
> +     command, we always want to print the line that is actually referred
> +     to by the address in the linetable.  That's why we always pass 0 here
> +     as second argument.  */
> +  symtab_and_line sal = find_pc_line (frame.value, 0);
> +
> +  if (should_print_location (print_what) || sal.symtab == nullptr)
> +    {
> +      gdb::unique_xmalloc_ptr<char> funname
> +	= find_pc_funname (frame.value);

This line fits in 80 columns and doesn't need to be broken.

> +      { /* Extra scope to print frame tuple.  */
> +	ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame");
> +
> +	annotate_shadowstack_frame_begin (frame.level, gdbarch,
> +					  frame.value);
> +
> +	uiout->text ("#");
> +	uiout->field_fmt_signed (2, ui_left, "level", frame.level);
> +
> +	annotate_shadowstack_frame_address ();
> +
> +	/* 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 (gdbarch);
> +
> +	uiout->field_string
> +	  ("addr", hex_string_custom (frame.value, element_size * 2),
> +	   address_style.style ());
> +
> +	annotate_shadowstack_frame_address_end ();
> +
> +	uiout->text (" in ");
> +	print_funname (uiout, funname, true);
> +
> +	if (print_what != SHORT_LOCATION && sal.symtab != nullptr)
> +	  print_filename (uiout, sal, true);
> +
> +	if (print_what != SHORT_LOCATION
> +	    && (funname == nullptr || sal.symtab == nullptr)
> +	    && sal.pspace != nullptr)
> +	  {
> +	    const char *lib = solib_name_from_address (sal.pspace,
> +						       frame.value);

This line fits in 80 columns and doesn't need to be broken.

> +	    if (lib != nullptr)
> +	      print_lib (uiout, lib, true);
> +	  }
> +      } /* Extra scope to print frame tuple.  */
> +
> +      uiout->text ("\n");
> +    }
> +
> +  if (print_what == SRC_LINE || print_what == SRC_AND_LOC)
> +    {
> +      int mid_statement = pc_in_middle_of_statement (frame.value, sal);

mid_statement should be a bool.

> +
> +      /* While for the ordinary backtrace printing of pc is based on
> +	 MID_STATEMENT determined by stack.c:frame_show_address and the
> +	 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, gdbarch, frame.value, sal, print_address,
> +		    mid_statement, "");
> +    }
> +
> +  annotate_shadowstack_frame_end ();
> +  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
> +  (gdbarch *gdbarch, const shadow_stack_print_options &print_options,
> +   const shadow_stack_frame_info &frame, print_what print_what)
> +{
> +  do_with_buffered_output
> +    (do_print_shadow_stack_frame_info, current_uiout, gdbarch,
> +     print_options, frame, print_what);
> +}
> +
> +
> +/* Extract a char array which can be used for printing a reasonable
> +   error message for REASON.  Note that in case REASON has the value
> +   NO_ERROR the returned array is empty.  */
> +
> +static const char *
> +ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
> +{
> +  switch (reason)
> +    {
> +    case ssp_unwind_stop_reason::no_error:
> +      return _("");

The empty string doesn't need to be translated. All callers make sure
that reason isn't no_error, so perhaps just remove this case?

> +    case ssp_unwind_stop_reason::memory_read_error:
> +      return _("shadow stack memory read failure");
> +    }
> +
> +  gdb_assert_not_reached ("invalid reason");
> +}
> +
> +
> +/* Read the memory at shadow stack pointer SSP and assign it to
> +   RETURN_VALUE.  In case we cannot read the memory, set REASON to
> +   ssp_unwind_stop_reason::memory_read_error and return false.  */
> +
> +static bool
> +read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
> +			  CORE_ADDR *return_value,
> +			  ssp_unwind_stop_reason *reason)

If a function assumes that a pointer it gets as argument is non-null as
this one does with return_value and reason, then IMO it's better to get
it as a reference instead of a pointer. Then the compiler makes sure
that it is indeed non-null.

> +{
> +  /* 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);
> +  if (!safe_read_memory_unsigned_integer (ssp, element_size, byte_order,
> +					  return_value))
> +    {
> +      *reason = ssp_unwind_stop_reason::memory_read_error;
> +      return false;
> +    }
> +
> +  return true;
> +}
> +
> +/*  If possible, return a shadow stack frame info which is COUNT elements
> +    above the bottom of the shadow stack.  FRAME should point to the top
> +    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.

Here is another place where I find "above/bottom/top" confusing, since
stacks can grow up or grow down (as pointed out in the next comment in
this function), and these terms mean different things in each case.

> +    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
> +  (gdbarch *gdbarch, const std::pair<CORE_ADDR, CORE_ADDR> range,
> +   const ULONGEST count, shadow_stack_frame_info &frame)
> +{
> +  /* Compute the number of bytes on the shadow stack, starting at
> +     FRAME->SSP, which depends on the direction the shadow stack
> +     grows.  */
> +  const int element_size
> +    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
> +  const unsigned long shadow_stack_bytes
> +    = (gdbarch_stack_grows_down (gdbarch))

The parentheses around the function call are superfluous.

> +       ? range.second - frame.ssp : frame.ssp - range.first + element_size;
> +
> +  gdb_assert ((shadow_stack_bytes % element_size) == 0);
> +  const unsigned long shadow_stack_size
> +    = shadow_stack_bytes / element_size;

This line fits 80 columns and doesn't need to be broken.

> +  const long level = shadow_stack_size - count;

In a comment further below you mention that level is 0-based, but
doesn't this expression mean that it's 1-based? Perhaps it's missing a
"- 1" term?

Also, this doesn't work for GCS because it assumes all elements in the
stack are return addresses. In GCS, the oldest element is a 0 entry.
The 0 entry is why this patch requires AArch64 to implement its own
gdbarch top_addr_empty_shadow_stack hook.

Finally, not a concern for userspace support (and thus this patch
series) but for OS and hypervisor software there's another kind of GCS
entry which is the exception return record that is put on the stack when
an exception is taken. That entry is 32 bytes in size. I mention this
just to illustrate that calculating the number of entries in the stack
can be non-trivial.

Not sure how to account for these variations in generic code. Maybe add
a new gdbarch method for returning the number of entries in the shadow
stack?

> +
> +  /* COUNT exceeds the number of elements on the shadow stack.  Return the
> +     starting shadow stack frame info FRAME.  */
> +  if (level <= 0)
> +    return std::optional<shadow_stack_frame_info> (frame);
> +
> +  CORE_ADDR new_ssp = update_shadow_stack_pointer
> +    (gdbarch, frame.ssp, count, ssp_update_direction::bottom);

Shouldn't update_shadow_stack_pointer be called with 'level' rather than
'count' as argument?

> +  if (gdbarch_stack_grows_down (gdbarch))
> +    gdb_assert (new_ssp < range.second);
> +  else
> +    gdb_assert (new_ssp >= range.first);
> +
> +  CORE_ADDR new_value;
> +  if (!read_shadow_stack_memory (gdbarch, new_ssp, &new_value,
> +				 &frame.unwind_stop_reason))
> +    return {};
> +
> +  return std::optional<shadow_stack_frame_info>
> +    ({new_ssp, new_value, (ULONGEST) level,
> +      ssp_unwind_stop_reason::no_error});

This line causes a compilation error in arm-linux-gnueabihf, and I
suppose other 32-bit targets as well:

/home/bauermann/src/binutils-gdb/gdb/shadow-stack.c:471:27: error: narrowing conversion of ‘(ULONGEST)((long int)level)’ from ‘ULONGEST’ {aka ‘long long unsigned int’} to ‘long unsigned int’ [-Werror=narrowing]
  471 |     ({new_ssp, new_value, (ULONGEST) level, ssp_unwind_stop_reason::no_error});
      |                           ^~~~~~~~~~~~~~~~

Also, it fits 80 columns and doesn't need to be broken.

> +}
> +
> +std::optional<shadow_stack_frame_info>
> +shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info
> +  (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range)
> +{
> +  /* 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 (gdbarch, this->ssp, 1,
> +				   ssp_update_direction::bottom);
> +
> +  if (gdbarch_stack_grows_down (gdbarch))

To make this work for GCS, I had to add another if statement before the
one above, to handle the case where new_ssp is at the initial 0 value:

  if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
      && gdbarch_top_addr_empty_shadow_stack (gdbarch, new_ssp, range))
    return {};
  else if (gdbarch_stack_grows_down (gdbarch))
    ⋮

Otherwise "bt shadow" will print the 0 entry:

(gdb) bt shadow
#0  0x0000aaaaaaaa08ac in call2 at aarch64-gcs-return.c:65
#1  0x0000aaaaaaaa08c0 in call1 at aarch64-gcs-return.c:71
#2  0x0000aaaaaaaa09d4 in main at aarch64-gcs-return.c:110
#3  0x0000000000000000 in ??

> +    {
> +      /* The shadow stack grows downwards.  */
> +      if (new_ssp >= range.second)
> +	{
> +	  /* We reached the bottom of the shadow stack.  */

In this case, we reached the top actually. This is why I find it
confusing to use bottom/top as if it where agnostic to whether the stack
grows up or down...

> +	  return {};
> +	}
> +      /* We updated new_ssp towards the bottom 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 bottom of the shadow stack.  */
> +	  return {};
> +	}
> +      /* We updated new_ssp towards the bottom of the shadow stack before,
> +	 and new_ssp must be pointing to shadow stack memory.  */
> +      gdb_assert (new_ssp <= range.second);
> +    }
> +
> +  CORE_ADDR new_value;
> +  if (!read_shadow_stack_memory (gdbarch, new_ssp, &new_value,
> +				 &this->unwind_stop_reason))
> +    return {};
> +
> +  return std::optional<shadow_stack_frame_info>
> +    ({new_ssp, new_value, this->level + 1,
> +      ssp_unwind_stop_reason::no_error});

This line fits 80 columns and doesn't need to be broken.

> +}
> +
> +/* Print all elements on the shadow stack or just the innermost COUNT_EXP
> +   frames.  */
> +
> +static void
> +backtrace_shadow_command (const shadow_stack_print_options &print_options,
> +			  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))
> +    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 target."));

At least for AArch64, GCS can be enabled or disabled per-thread so
I would say "not enabled for the current thread" here.

> +  /* 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.  */

Hm, I'm not sure if aarch64-linux supports GCS switching. The kernel's
gcs.rst documentation says:

  * The architecture provides instructions for switching between guarded
    control stacks with checks to ensure that the new stack is a valid
    target for switching.

And a comment in AArch64's implementation of the map_shadow_stack
syscall says:

  /*
   * Put a cap token at the end of the allocated region so it
   * can be switched to.
   */

But I don't see anything positively mention that it's supported.
I assume so, but I'll have to confirm. If that is the case, later I will
submit a patch with any necessary changes.

> +  std::pair<CORE_ADDR, CORE_ADDR> range;
> +  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
> +						     &range))

Shouldn't this if condition also check gdbarch_top_addr_empty_shadow_stack?

> +    {
> +      /* If the current shadow stack pointer does not point to shadow
> +	 stack memory, the shadow stack is empty.  */
> +      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;
> +  CORE_ADDR new_value;
> +  if (read_shadow_stack_memory (gdbarch, *start_ssp, &new_value, &reason))
> +    current = {*start_ssp, new_value, 0,
> +	       ssp_unwind_stop_reason::no_error};

This line fits in 80 columns and doesn't need to be broken.

> +
> +  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.  */

Actually, trailing will be the frame above the one from which we should
start printing. For example, if count is -3
get_trailing_outermost_shadow_stack_frame_info will return the frame
"which is COUNT elements above the bottom of the shadow stack", which in
this case is the 4th frame counting from the oldest frame. But we need
to start printing from the 3rd oldest frame ...

> +      if (count < 0)
> +	{
> +	  trailing = get_trailing_outermost_shadow_stack_frame_info
> +		       (gdbarch, range, std::abs (count), *current);

... so this call should pass std::abs (count) - 1 as argument.

> +	  if (!trailing.has_value ())
> +	    reason = current->unwind_stop_reason;
> +	}
> +    }
> +
> +  if (!trailing.has_value ())
> +    {
> +      if (reason > ssp_unwind_stop_reason::no_error)
> +	error (_("Cannot print shadow stack backtrace: %s.\n"),
> +	       ssp_unwind_stop_reason_to_err_string (reason));
> +      else
> +	gdb_assert_not_reached ("invalid reason");
> +    }

It's clearer to write this as:

  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));
    }

> +
> +  current = trailing;
> +  while (current.has_value () && count--)

The GDB Coding Standard says that comparing a number to zero should be
explicit, so "count-- != 0" here. I see that backtrace_command_1 already
has the implicit comparison, but it's probably a mistake there (or just
old code).

Also, another way of expressing the above would be:

  for (current = trailing; current.has_value () && count != 0; count--)

I personally think it's clearer (especially because the comparison and
decrement are separate), but it's more of a personal preference so
I don't really mind either way.

> +    {
> +      QUIT;
> +
> +      print_shadow_stack_frame_info (gdbarch, print_options, *current,
> +				     LOCATION);
> +
> +      trailing = current;
> +      current = current->unwind_prev_shadow_stack_frame_info (gdbarch,
> +							      range);

This line fits in 80 columns and doesn't need to be broken.

> +    }
> +
> +  /* If we've stopped before the end, mention that.  */
> +  if (current && from_tty)

While it's correct to use an std::optional in this way to check whether
it has a value, IMHO it's clearer to be more explicit and use the
has_value method. It's also consistent with the rest of the code in this
function.

> +    gdb_printf (_("(More shadow stack frames follow...)\n"));
> +
> +  /* 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.has_value ()

If I'm not mistaken, trailing.has_value () is always true at this point.

> +      && (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 (gdbarch, trailing->ssp),
> +		ssp_unwind_stop_reason_to_err_string
> +		  (trailing->unwind_stop_reason));
> +}

<snip>

> diff --git a/gdb/stack.c b/gdb/stack.c
> index 40756f74a00..30a7d8621be 100644
> --- a/gdb/stack.c
> +++ b/gdb/stack.c
> @@ -50,6 +50,7 @@
>  #include "linespec.h"
>  #include "cli/cli-utils.h"
>  #include "objfiles.h"
> +#include "shadow-stack.h"
>  
>  #include "symfile.h"
>  #include "extension.h"
> @@ -86,7 +87,7 @@ const char print_frame_info_source_and_location[] = "source-and-location";
>  const char print_frame_info_location_and_address[] = "location-and-address";
>  const char print_frame_info_short_location[] = "short-location";
>  
> -static const char *const print_frame_info_choices[] =
> +const char *const print_frame_info_choices[] =
>  {
>    print_frame_info_auto,
>    print_frame_info_source_line,
> @@ -962,11 +963,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++)
> @@ -1016,7 +1015,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.  */

Change comment here to /* See stack.h.  */

> -static bool
> +bool
>  should_print_location (print_what print_what)
>  {
>    return (print_what == LOCATION
> @@ -1025,14 +1024,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)
> @@ -1298,7 +1292,7 @@ get_last_displayed_sal ()
>  
>  /* Find the function name for the symbol SYM.  */

Change comment here to /* See stack.h.  */

>  
> -static gdb::unique_xmalloc_ptr<char>
> +gdb::unique_xmalloc_ptr<char>
>  find_symbol_funname (const symbol *sym)
>  {
>    gdb::unique_xmalloc_ptr<char> funname;

<snip>

> +/* Completer for the "backtrace shadow" sub-command.  */
> +
> +static void
> +backtrace_shadow_command_completer (struct cmd_list_element *ignore,
> +				    completion_tracker &tracker,
> +				    const char *text, const char */*word*/)
> +{
> +  const auto group
> +    = make_backtrace_shadow_options_def_group (nullptr);
> +  if (gdb::option::complete_options
> +      (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group))
> +    return;
> +
> +  const char *word = advance_to_expression_complete_word_point (tracker, text);
> +  expression_completer (ignore, tracker, text, word);
> +}

I would put this function in shadow-stack.c.

<snip>

> diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> index 0ae172d7c41..4563e49d9e4 100644
> --- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> @@ -109,6 +109,94 @@ 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" ""]

I suggest adding a descriptive test name to the get_valueof calls above.
Perhaps "read newest shadow stack entry at main/call1/call2"?

> +    set frame0 "#0\[ \t\]*$sspval_call2 in call1$fill"
> +    set frame1 "#1\[ \t\]*$sspval_call1 in main$fill"
> +    set frame2 "#2\[ \t\]*$sspval_main$fill"
> +
> +    # We can 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" \
> +	[multi_line \
> +	    "$frame0" \
> +	    "$frame1" \
> +	    "$frame2" \
> +	    ".*" ] \
> +	"test shadow stack backtrace until the bottom of the stack."
> +
> +    gdb_test "bt shadow 2" \
> +	[multi_line \
> +	    "$frame0" \
> +	    "$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"

In my GCS testcase this test is passing, even though the command is
printing more than one frame:

bt shadow -1
#3  0x0000aaaaaaaa08c0 in call1 at /home/bauermann/src/binutils-gdb/gdb/testsuite/gdb.arch/aarch64-gcs-return.c:71
#4  0x0000aaaaaaaa09d4 in main at /home/bauermann/src/binutils-gdb/gdb/testsuite/gdb.arch/aarch64-gcs-return.c:110
#5  0x0000000000000000 in ??
(gdb) PASS: gdb.arch/aarch64-gcs-backtrace.exp: test shadow stack backtrace with a negative value for count

So there's one bug in GDB, and one in the testcase. To fix the testcase
bug, I believe you just have to add '^' to the beginning of the pattern.

The bug in GDB was fixed by changing the expression to calculate 'level'
in get_trailing_outermost_shadow_stack_frame_info as I mentioned above.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-10-13  7:19         ` Schimpe, Christina
@ 2025-10-31  4:39           ` Thiago Jung Bauermann
  2025-11-06 14:23             ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-10-31  4:39 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Eli Zaretskii, gdb-patches

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

>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Monday, October 13, 2025 3:17 AM
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: Eli Zaretskii <eliz@gnu.org>; gdb-patches@sourceware.org
>> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
>> stack backtrace.
>> 
>> Hello,
>> 
>> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
>> 
>> >> -----Original Message-----
>> >> From: Eli Zaretskii <eliz@gnu.org>
>> >> Sent: Tuesday, September 23, 2025 1:47 PM
>> >> To: Schimpe, Christina <christina.schimpe@intel.com>
>> >> Cc: gdb-patches@sourceware.org
>> >> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the
>> >> shadow stack backtrace.
>> >>
>> >> > From: Christina Schimpe <christina.schimpe@intel.com>
>> >> > Date: Tue, 23 Sep 2025 11:18:39 +0000
>> >> >
>> >> > Add a subcommand 'bt shadow' for the ordinary backtrace command
>> >> > which prints the shadow stack backtrace.
>> >> > Similar to the ordinary backtrace command 'bt shadow' can be
>> >> > configured using COUNT and the command line option -frame-info.
>> >> > However, we always print the address and the command is not
>> >> > affected by the setting "print address" as well as the setting
>> >> > "print frame-info
>> >> location-and-address".
>> >> > Also we do not print the frame arguments.
>> >> >
>> >> > Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]
>> >> >
>> >> > Help output:
>> >> > ~~
>> >> > (gdb) help bt shadow
>> >> > Print backtrace of all shadow stack frames, or innermost COUNT frames.
>> >> > Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
>> >>
>> >> We have just got rid of qualifier arguments like "full"
>> >> and "hide".  Instead of introducing qualifier arguments anew, why not
>> >> go the way of thread-related commands and add a new command
>> "shadow",
>> >> so the user could say "shadow backtrace" and maybe in the future also
>> >> other sub- commands?
>> >
>> > This has been discussed in a thread years ago:
>> > https://sourceware.org/pipermail/gdb/2023-December/051024.html
>> > The direction was to make it part of the ordinary bt command.

I think Eli has a good point here, about "bt shadow" going in the
opposite direction of the tendency in the backtrace command of moving
away from subcommands. From its help entry:

(gdb) help backtrace
        ⋮
For backward compatibility, the following qualifiers are supported:

   full       - same as -full option.
   no-filters - same as -no-filters option.
   hide       - same as -hide.

One way to address that is what Eli suggested, but another is to go with
a "-shadow" option.

>> > Currently, we don't plan any further commands for the shadow stack.
>> > But maybe for ARM's Guarded Control Stack any further options are
>> planned?
>> > I added Thiago in cc here. Maybe he has some more input.
>> 
>> Yes, I plan to add two commands for AArch64 Guarded Control Stack, as
>> mentioned in that thread:
>> 
>> https://inbox.sourceware.org/gdb/87h6kbfazh.fsf@linaro.org/
>> 
>> In that email, I suggested putting them under "info shadow-stack", but later
>> the people in the thread considered that just adding a top-level "shadow-
>> stack" command would be better.
>> 
>> I do think that "backtrace -shadow" is a good option, so IMO we could have
>> both it and the "shadow-stack" umbrella command. It could even have
>> "shadow-stack backtrace" as an alias to "backtrace -shadow" to improve
>> discoverability.
>
> I have nothing against that I think, are there similar commands in GDB that have such an alias feature?

There are some tui subcommands that have toplevel aliases, such as
"refresh" → "tui refresh", and "winheight" → "tui winheight".

>> You mentioned that x86 doesn't need the "enabled" and "locked" commands
>> because for that architecture the user can look in /proc/$PID/status, but with
>> those commands I also plan to allow the user to change the enabled and
>> locked GCS features. Wouldn't that be useful for x86?
>
> For x86 the user can configure the shadow stack enabled and locked state via arch_prctls:
> https://docs.kernel.org/next/x86/shstk.html
>
> Is that possible for GCS, too?
> I am not sure if we can easily change it using GDB. We currently only read/write the
> shadow stack pointer via ptrace.
>
> But it is possible to see those states by running "info proc status" in gdb:
> [...]
> x86_Thread_features:	shstk 
> x86_Thread_features_locked:	shstk wrss
>
> So I'd say we don't really need it, but it might be useful to improve discoverability, too.

I did a quick experiment making an inferior function call to
prctl (75, 0) from GDB but that didn't disable GCS in the inferior. I'll
try again tomorrow.

But even if it worked, IMO it's a cumbersome way of getting/setting this information.
Also, AArch64 doesn't have this information in "info proc status".

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2025-09-25 11:10     ` Schimpe, Christina
@ 2025-11-02 21:20       ` Thiago Jung Bauermann
  2025-11-12 17:28         ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-11-02 21:20 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Eli Zaretskii, gdb-patches

Hello,

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

>> -----Original Message-----
>> From: Eli Zaretskii <eliz@gnu.org>
>> Sent: Tuesday, September 23, 2025 1:50 PM
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: gdb-patches@sourceware.org
>> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow
>> stack backtrace elements.
>> 
>> > From: Christina Schimpe <christina.schimpe@intel.com>
>> > Date: Tue, 23 Sep 2025 11:18:40 +0000
>> >
>> > On x86 with CET there can be elements on the shadow stack which are
>> > not return addresses.  In this case, we just want to print the element
>> > itself in the shadow stack backtrace, but no further information.
>> >
>> > Provide a gdbarch hook to distinguish between return and non-return
>> > addresses and use it to print the shadow stack backtrace as described
>> > above.
>> > ---
>> >  gdb/doc/gdb.texinfo       | 19 ++++++++++++
>> >  gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
>> >  gdb/gdbarch-gen.h         | 15 +++++++++
>> >  gdb/gdbarch.h             |  1 +
>> >  gdb/gdbarch_components.py | 17 +++++++++++
>> >  gdb/shadow-stack.c        | 64 +++++++++++++++++----------------------
>> >  gdb/shadow-stack.h        | 37 ++++++++++++++++++++++
>> >  7 files changed, 148 insertions(+), 37 deletions(-)
>> 
>> Thanks.
>> 
>> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
>> > ebda4546b58..a0fde385a8e 100644
>> > --- a/gdb/doc/gdb.texinfo
>> > +++ b/gdb/doc/gdb.texinfo
>> > @@ -8887,6 +8887,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
>>                ^
>> A cross-reference is missing here.
>
> Thanks will fix.
>
>> > +in a special format with bit 63 set.  For such shadow stack elements,
>> > +the shadow stack frame just contains the level and the address on the
>> > +shadow stack, as shown in the following example by frame 1:
>> > +
>> > +@smallexample
>> > +@group
>> > +(gdb) bt shadow 4
>> > +#0  0x00007ffff7c54d90 in __restore_rt from /lib64/libc.so.6
>> > +#1  0x80007ffff79fffd8
>> > +#2  0x00007ffff7c54ce6 in __GI_raise at ../sysdeps/posix/raise.c:27
>> > +#3  0x000000000040115d in main at /tmp/amd64-shadow-stack-
>> signal.c:32
>> > +(More shadow stack frames follow...)
>> > +@end group
>> > +@end smallexample
>> 
>> Would it make sense to show something like "<signal caught>", instead of a
>> frame with only an address?
>
> Yeah, this is a good idea, I wondered about a similar thing actually but wanted to discuss
> the general direction for handling those specific elements on the shadow stack first.
> Maybe there are more options on other architectures that we have to consider.
> Let's wait for more feedback on this, I added Thiago again in cc here.

Yes, I would like to show a "<signal handler called>" message in the
corresponding shadow stack entry, as is done in the regular stack:

  (gdb) bt
  #0  handler (signo=10) at aarch64-gcs-signal.c:59
  #1  <signal handler called>
  #2  __pthread_kill_implementation (threadid=281474842447584, signo=signo@entry=10, no_tid=no_tid@entry=0)
      at ./nptl/pthread_kill.c:44
  #3  0x0000fffff7e77670 in __pthread_kill_internal (signo=10, threadid=<optimized out>) at ./nptl/pthread_kill.c:78
  #4  0x0000fffff7e2cb3c in __GI_raise (sig=10) at ../sysdeps/posix/raise.c:26
  #5  0x0000aaaaaaaa0b24 in main () at aarch64-gcs-signal.c:96

Here is the corresponding GCS backtrace:

  (gdb) bt shadow
  #0  0x0000fffff7ffa85c in __kernel_rt_sigreturn
  #1  0x0000fffff7bff000 in ??
  #2  0x0000fffff7e2cb3c in __GI_raise at ../sysdeps/posix/raise.c:27
  #3  0x0000aaaaaaaa0b24 in main at aarch64-gcs-signal.c:99

In this example "<signal handler called>" message would replace the VDSO
function name in entry #0. Also note that it's an actual return address
but we still want to show a custom message for it.

Finally, notice that there's GCS entry #1, which isn't a return
address. It's a "cap record", which marks that the stack is currently
not in use. When a signal is delivered, the kernel pushes two entries to
the thread's GCS: first the cap record, then the address to the signal
trampoline (__kernel_rt_sigreturn). For this entry, I'd like to show
"<cap record>".

One suggestion to handle that is to make
do_print_shadow_stack_frame_info call a new hook
gdbarch_print_shadow_stack_frame_info which would allow the architecture
to print the shadow stack frame however it wants. If the hook returns
false, then do_print_shadow_stack_frame_info would print the information
itself, as it does in your patch. What do you think?

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-10-29 15:28               ` Guinevere Larsen
@ 2025-11-03 19:47                 ` Schimpe, Christina
  2025-11-04 11:53                   ` Guinevere Larsen
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-11-03 19:47 UTC (permalink / raw)
  To: Guinevere Larsen, 'Simon Marchi', 'Eli Zaretskii'
  Cc: 'gdb-patches@sourceware.org',
	'thiago.bauermann@linaro.org'

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Mittwoch, 29. Oktober 2025 16:28
> To: Schimpe, Christina <christina.schimpe@intel.com>; 'Simon Marchi'
> <simark@simark.ca>; 'Eli Zaretskii' <eliz@gnu.org>
> Cc: 'gdb-patches@sourceware.org' <gdb-patches@sourceware.org>;
> 'thiago.bauermann@linaro.org' <thiago.bauermann@linaro.org>
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack
> backtrace.
> 
> On 10/29/25 12:05 PM, Schimpe, Christina wrote:
> > Kindly pinging for feedback on this discussion to clarify if we should
> > better use
> > - "bt -shadow" (command line option)
> > - "bt shadow" (subcommand approach, current implementation).
> >
> > My personal opinion is still to use the subcommand approach, but, as I
> > already said, I think "bt -shadow" would be fine, too.
> >
> > Please also see this discussion with Thiago:
> > https://sourceware.org/pipermail/gdb-patches/2025-October/221660.html
> 
> If it makes any difference, I personally prefer the command line option
> approach. That's based on looking at how record, record full and record btrace
> works, it is much easier to understand the code if the decision is made on the
> command's function itself

Hi Guinevere,

I might be missing something here. Aren’t record, record full, and record btrace considered subcommands?
If that’s the case, wouldn’t the example of the record command actually support the subcommand approach? Or are you pointing to record as a negative example?

About your comment:
"it is much easier to understand the code if the decision is made on the command's function itself"
Could you clarify what you mean? Are you referring to making it easier for a reviewer or developer to go through my patches?
From my perspective, the most important thing is that the GDB user can find the subcommand or command-line option easily and intuitively.

Looking forward to your thoughts!
Christina
Intel Deutschland GmbH
Registered Address: Dornacher Straße 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht München HRB 186928

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-11-03 19:47                 ` Schimpe, Christina
@ 2025-11-04 11:53                   ` Guinevere Larsen
  2025-11-05 16:33                     ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Guinevere Larsen @ 2025-11-04 11:53 UTC (permalink / raw)
  To: Schimpe, Christina, 'Simon Marchi', 'Eli Zaretskii'
  Cc: 'gdb-patches@sourceware.org',
	'thiago.bauermann@linaro.org'

On 11/3/25 4:47 PM, Schimpe, Christina wrote:
>> -----Original Message-----
>> From: Guinevere Larsen <guinevere@redhat.com>
>> Sent: Mittwoch, 29. Oktober 2025 16:28
>> To: Schimpe, Christina <christina.schimpe@intel.com>; 'Simon Marchi'
>> <simark@simark.ca>; 'Eli Zaretskii' <eliz@gnu.org>
>> Cc: 'gdb-patches@sourceware.org' <gdb-patches@sourceware.org>;
>> 'thiago.bauermann@linaro.org' <thiago.bauermann@linaro.org>
>> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack
>> backtrace.
>>
>> On 10/29/25 12:05 PM, Schimpe, Christina wrote:
>>> Kindly pinging for feedback on this discussion to clarify if we should
>>> better use
>>> - "bt -shadow" (command line option)
>>> - "bt shadow" (subcommand approach, current implementation).
>>>
>>> My personal opinion is still to use the subcommand approach, but, as I
>>> already said, I think "bt -shadow" would be fine, too.
>>>
>>> Please also see this discussion with Thiago:
>>> https://sourceware.org/pipermail/gdb-patches/2025-October/221660.html
>> If it makes any difference, I personally prefer the command line option
>> approach. That's based on looking at how record, record full and record btrace
>> works, it is much easier to understand the code if the decision is made on the
>> command's function itself
> Hi Guinevere,
>
> I might be missing something here. Aren’t record, record full, and record btrace considered subcommands?
> If that’s the case, wouldn’t the example of the record command actually support the subcommand approach? Or are you pointing to record as a negative example?

I am using it as a negative example, yes, sorry if it was confusing.

record full and record btrace are subcommands of record, and I found it 
needlessly confusing to figure out how the code decided what to do. I 
didn't even know that commands with subcommands were supported to be 
quite honest, so I expected "full" and "btrace" to be handled like 
options, and was quite confused when that wasn't the case.

>
> About your comment:
> "it is much easier to understand the code if the decision is made on the command's function itself"
> Could you clarify what you mean? Are you referring to making it easier for a reviewer or developer to go through my patches?
>  From my perspective, the most important thing is that the GDB user can find the subcommand or command-line option easily and intuitively.

I mean making it easier to maintain as a GDB developer, yes. However, I 
do think this will also make things more confusing to end-users. Right 
now, backtrace accepts '-' prefixed options and a few non '-' prefixed 
options for backwards compatibility. from the POV of an end-user who 
doesn't know the difference between a command option and a subcommand, 
you'd be introducing a new option in the backwards compatible (but not 
preferred) style. Or worse, they'd think that "backtrace full", 
"backtrace no-filters" and "backtrace hide" were also subcommands but 
now the list of backtrace subcommands would no longer show those,  which 
makes it sound like they stopped being supported or that the help text 
is incorrect.

-- 
Cheers,
Guinevere Larsen
It/she


>
> Looking forward to your thoughts!
> Christina
> Intel Deutschland GmbH
> Registered Address: Dornacher Straße 1, 85622 Feldkirchen, Germany
> Tel: +49 89 991 430, www.intel.de
> Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Seat: Munich
> Commercial Register: Amtsgericht München HRB 186928
>


^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-11-04 11:53                   ` Guinevere Larsen
@ 2025-11-05 16:33                     ` Schimpe, Christina
  0 siblings, 0 replies; 67+ messages in thread
From: Schimpe, Christina @ 2025-11-05 16:33 UTC (permalink / raw)
  To: Guinevere Larsen, 'Simon Marchi', 'Eli Zaretskii',
	'thiago.bauermann@linaro.org'
  Cc: 'gdb-patches@sourceware.org'

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Dienstag, 4. November 2025 12:54
> To: Schimpe, Christina <christina.schimpe@intel.com>; 'Simon Marchi'
> <simark@simark.ca>; 'Eli Zaretskii' <eliz@gnu.org>
> Cc: 'gdb-patches@sourceware.org' <gdb-patches@sourceware.org>;
> 'thiago.bauermann@linaro.org' <thiago.bauermann@linaro.org>
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack
> backtrace.
> 
> On 11/3/25 4:47 PM, Schimpe, Christina wrote:
> >> -----Original Message-----
> >> From: Guinevere Larsen <guinevere@redhat.com>
> >> Sent: Mittwoch, 29. Oktober 2025 16:28
> >> To: Schimpe, Christina <christina.schimpe@intel.com>; 'Simon Marchi'
> >> <simark@simark.ca>; 'Eli Zaretskii' <eliz@gnu.org>
> >> Cc: 'gdb-patches@sourceware.org' <gdb-patches@sourceware.org>;
> >> 'thiago.bauermann@linaro.org' <thiago.bauermann@linaro.org>
> >> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the
> >> shadow stack backtrace.
> >>
> >> On 10/29/25 12:05 PM, Schimpe, Christina wrote:
> >>> Kindly pinging for feedback on this discussion to clarify if we
> >>> should better use
> >>> - "bt -shadow" (command line option)
> >>> - "bt shadow" (subcommand approach, current implementation).
> >>>
> >>> My personal opinion is still to use the subcommand approach, but, as
> >>> I already said, I think "bt -shadow" would be fine, too.
> >>>
> >>> Please also see this discussion with Thiago:
> >>> https://sourceware.org/pipermail/gdb-patches/2025-
> October/221660.htm
> >>> l
> >> If it makes any difference, I personally prefer the command line
> >> option approach. That's based on looking at how record, record full
> >> and record btrace works, it is much easier to understand the code if
> >> the decision is made on the command's function itself
> > Hi Guinevere,
> >
> > I might be missing something here. Aren’t record, record full, and record
> btrace considered subcommands?
> > If that’s the case, wouldn’t the example of the record command actually
> support the subcommand approach? Or are you pointing to record as a
> negative example?
> 
> I am using it as a negative example, yes, sorry if it was confusing.
> 
> record full and record btrace are subcommands of record, and I found it
> needlessly confusing to figure out how the code decided what to do. I didn't
> even know that commands with subcommands were supported to be quite
> honest, so I expected "full" and "btrace" to be handled like options, and was
> quite confused when that wasn't the case.
> >
> > About your comment:
> > "it is much easier to understand the code if the decision is made on the
> command's function itself"
> > Could you clarify what you mean? Are you referring to making it easier for a
> reviewer or developer to go through my patches?
> >  From my perspective, the most important thing is that the GDB user can find
> the subcommand or command-line option easily and intuitively.
> 
> I mean making it easier to maintain as a GDB developer, yes. However, I do
> think this will also make things more confusing to end-users. Right now,
> backtrace accepts '-' prefixed options and a few non '-' prefixed options for
> backwards compatibility. from the POV of an end-user who doesn't know the
> difference between a command option and a subcommand, you'd be
> introducing a new option in the backwards compatible (but not
> preferred) style. Or worse, they'd think that "backtrace full", "backtrace no-
> filters" and "backtrace hide" were also subcommands but now the list of
> backtrace subcommands would no longer show those,  which makes it sound
> like they stopped being supported or that the help text is incorrect.

This is how "bt shadow" is shown in the help text of "help bt"

~~~
(gdb) help bt
backtrace, where, bt
Print backtrace of all stack frames, or innermost COUNT frames.
Usage: backtrace [OPTION]... [QUALIFIER]... [COUNT | -COUNT]

Options:
  -entry-values no|only|preferred|if-needed|both|compact|default
   [..]
  -hide
    Causes Python frame filter elided frames to not be printed.

For backward compatibility, the following qualifiers are supported:

   full       - same as -full option.
   no-filters - same as -no-filters option.
   hide       - same as -hide.

With a negative COUNT, print outermost -COUNT frames.

List of "backtrace" subcommands:

backtrace shadow -- Print backtrace of all shadow stack frames, or innermost COUNT frames.
~~~

I had assumed this was clear from the help text, but I understand your perspective, too.

Here is the summarized pro/con list:

Command-line option: bt -shadow

Pros:
- Matches the general trend of moving away from subcommands for backtrace. (Eli, Thiago)
- Easier for GDB developers to maintain. (Guinevere)

Cons:
- Existing backtrace options affect output but not the algorithm, unlike shadow stack backtrace.
  This makes -shadow harder to discover as new command type. (Christina)
- Some existing options don’t make sense when combined with -shadow. (Christina, Simon)

Subcommand: bt shadow

Pros:
- Indicates that the output is fundamentally different from the ordinary backtrace,
  even if it looks similar. (Christina)

Cons:
- backtrace is both a prefix and a regular command. Adding subcommands could cause
  conflicts if future options like "backtrace SYMBOL" are introduced. (Simon)
- Confusing for users who don’t understand the difference between options and
  subcommands. (Guinevere)

Since Simon, Thiago, Guinevere, and Eli seem to prefer the command-line option,
I will proceed in that direction for v2 of this series unless I hear otherwise in the
next couple of days.

This is my suggestion for the new help text:

~~~
(gdb) help bt
backtrace, where, bt
Print backtrace of all stack frames, or innermost COUNT frames.
Usage: backtrace [OPTION]... [QUALIFIER]... [COUNT | -COUNT]

Options:
  -entry-values no|only|preferred|if-needed|both|compact|default
   [..]
  -frame-arguments all|scalars|none|presence
    Set printing of non-scalar frame arguments.
  -raw-frame-arguments [on|off]
    [..]
  -frame-info auto|source-line|location|source-and-location
              |location-and-address|short-location
    Set printing of frame information.
  -past-main [on|off]
    [..]
  -past-entry [on|off]
    [..]
  -full
    Print values of local variables.
  -no-filters
    Prohibit frame filters from executing on a backtrace.
  -hide
    Causes Python frame filter elided frames to not be printed.- shadow
   Print shadow stack frames instead of normal frames.  This option may be combined
   with “-frame-info” and implies ‘-no-filters’ and ‘-frame-arguments none’.
- shadow
   Print shadow stack frames instead of normal frames.  This option may be combined
   with “-frame-info” and implies ‘-no-filters’ and ‘-frame-arguments none’.

For backward compatibility, the following qualifiers are supported:

   full       - same as -full option.
   no-filters - same as -no-filters option.
   hide       - same as -hide.

With a negative COUNT, print outermost -COUNT frames.
~~~

Thank you all for the valuable feedback!
Christina
Intel Deutschland GmbH
Registered Address: Dornacher Straße 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht München HRB 186928

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-10-31  4:39           ` Thiago Jung Bauermann
@ 2025-11-06 14:23             ` Schimpe, Christina
  0 siblings, 0 replies; 67+ messages in thread
From: Schimpe, Christina @ 2025-11-06 14:23 UTC (permalink / raw)
  To: Thiago Jung Bauermann
  Cc: Eli Zaretskii, gdb-patches, Simon Marchi, Guinevere Larsen

Hi Thiago,

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Friday, 31 October 2025 05:40
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: Eli Zaretskii <eliz@gnu.org>; gdb-patches@sourceware.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> >> -----Original Message-----
> >> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> Sent: Monday, October 13, 2025 3:17 AM
> >> To: Schimpe, Christina <christina.schimpe@intel.com>
> >> Cc: Eli Zaretskii <eliz@gnu.org>; gdb-patches@sourceware.org
> >> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the
> >> shadow stack backtrace.
> >>
> >> Hello,
> >>
> >> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> >>
> >> >> -----Original Message-----
> >> >> From: Eli Zaretskii <eliz@gnu.org>
> >> >> Sent: Tuesday, September 23, 2025 1:47 PM
> >> >> To: Schimpe, Christina <christina.schimpe@intel.com>
> >> >> Cc: gdb-patches@sourceware.org
> >> >> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the
> >> >> shadow stack backtrace.
> >> >>
> >> >> > From: Christina Schimpe <christina.schimpe@intel.com>
> >> >> > Date: Tue, 23 Sep 2025 11:18:39 +0000
> >> >> >
> >> >> > Add a subcommand 'bt shadow' for the ordinary backtrace command
> >> >> > which prints the shadow stack backtrace.
> >> >> > Similar to the ordinary backtrace command 'bt shadow' can be
> >> >> > configured using COUNT and the command line option -frame-info.
> >> >> > However, we always print the address and the command is not
> >> >> > affected by the setting "print address" as well as the setting
> >> >> > "print frame-info
> >> >> location-and-address".
> >> >> > Also we do not print the frame arguments.
> >> >> >
> >> >> > Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]
> >> >> >
> >> >> > Help output:
> >> >> > ~~
> >> >> > (gdb) help bt shadow
> >> >> > Print backtrace of all shadow stack frames, or innermost COUNT
> frames.
> >> >> > Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
> >> >>
> >> >> We have just got rid of qualifier arguments like "full"
> >> >> and "hide".  Instead of introducing qualifier arguments anew, why
> >> >> not go the way of thread-related commands and add a new command
> >> "shadow",
> >> >> so the user could say "shadow backtrace" and maybe in the future
> >> >> also other sub- commands?
> >> >
> >> > This has been discussed in a thread years ago:
> >> > https://sourceware.org/pipermail/gdb/2023-December/051024.html
> >> > The direction was to make it part of the ordinary bt command.
> 
> I think Eli has a good point here, about "bt shadow" going in the opposite
> direction of the tendency in the backtrace command of moving away from
> subcommands. From its help entry:
> 
> (gdb) help backtrace
>         ⋮
> For backward compatibility, the following qualifiers are supported:
> 
>    full       - same as -full option.
>    no-filters - same as -no-filters option.
>    hide       - same as -hide.
> 
> One way to address that is what Eli suggested, but another is to go with a "-
> shadow" option.

Thank you very much for the feedback!
Regarding “backtrace full”, I completely agree that it makes more sense to provide
this as a command-line option (e.g., backtrace -full), since -full is simply another
way to configure the output of the backtrace command, like -past-main.
But the option "-shadow" is based on a different algorithm.  

I’ve moved the conversation to this thread:
https://sourceware.org/pipermail/gdb-patches/2025-November/222374.html
It includes a summary of all collected feedback in the form of a pro/con list.
The majority opinion clearly leans toward ‘bt -shadow’.

> >> > Currently, we don't plan any further commands for the shadow stack.
> >> > But maybe for ARM's Guarded Control Stack any further options are
> >> planned?
> >> > I added Thiago in cc here. Maybe he has some more input.
> >>
> >> Yes, I plan to add two commands for AArch64 Guarded Control Stack, as
> >> mentioned in that thread:
> >>
> >> https://inbox.sourceware.org/gdb/87h6kbfazh.fsf@linaro.org/
> >>
> >> In that email, I suggested putting them under "info shadow-stack",
> >> but later the people in the thread considered that just adding a
> >> top-level "shadow- stack" command would be better.
> >>
> >> I do think that "backtrace -shadow" is a good option, so IMO we could
> >> have both it and the "shadow-stack" umbrella command. It could even
> >> have "shadow-stack backtrace" as an alias to "backtrace -shadow" to
> >> improve discoverability.
> >
> > I have nothing against that I think, are there similar commands in GDB that
> have such an alias feature?
> 
> There are some tui subcommands that have toplevel aliases, such as
> "refresh" → "tui refresh", and "winheight" → "tui winheight".
> 
> >> You mentioned that x86 doesn't need the "enabled" and "locked"
> >> commands because for that architecture the user can look in
> >> /proc/$PID/status, but with those commands I also plan to allow the
> >> user to change the enabled and locked GCS features. Wouldn't that be
> useful for x86?
> >
> > For x86 the user can configure the shadow stack enabled and locked state
> via arch_prctls:
> > https://docs.kernel.org/next/x86/shstk.html
> >
> > Is that possible for GCS, too?
> > I am not sure if we can easily change it using GDB. We currently only
> > read/write the shadow stack pointer via ptrace.
> >
> > But it is possible to see those states by running "info proc status" in gdb:
> > [...]
> > x86_Thread_features:	shstk
> > x86_Thread_features_locked:	shstk wrss
> >
> > So I'd say we don't really need it, but it might be useful to improve
> discoverability, too.
> 
> I did a quick experiment making an inferior function call to prctl (75, 0) from
> GDB but that didn't disable GCS in the inferior. I'll try again tomorrow.
> 
> But even if it worked, IMO it's a cumbersome way of getting/setting this
> information.
> Also, AArch64 doesn't have this information in "info proc status".

I understand that for Aarch64 the commands are necessary.
For now we don't see the need to implement them for CET, but we might
come back to this in case there are complaints.

I could implement them by parsing "info proc status" output, to enable at least
the reading of those states. But I rather hope that GDB users will be able to
find "info proc status".

Christina


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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2025-11-02 21:20       ` Thiago Jung Bauermann
@ 2025-11-12 17:28         ` Schimpe, Christina
  2025-11-16 18:39           ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-11-12 17:28 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: Eli Zaretskii, gdb-patches

Hi Thiago,

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Sonntag, 2. November 2025 22:20
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: Eli Zaretskii <eliz@gnu.org>; gdb-patches@sourceware.org
> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow
> stack backtrace elements.
> 
> Hello,
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> >> -----Original Message-----
> >> From: Eli Zaretskii <eliz@gnu.org>
> >> Sent: Tuesday, September 23, 2025 1:50 PM
> >> To: Schimpe, Christina <christina.schimpe@intel.com>
> >> Cc: gdb-patches@sourceware.org
> >> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish
> >> shadow stack backtrace elements.
> >>
> >> > From: Christina Schimpe <christina.schimpe@intel.com>
> >> > Date: Tue, 23 Sep 2025 11:18:40 +0000
> >> >
> >> > On x86 with CET there can be elements on the shadow stack which are
> >> > not return addresses.  In this case, we just want to print the
> >> > element itself in the shadow stack backtrace, but no further information.
> >> >
> >> > Provide a gdbarch hook to distinguish between return and non-return
> >> > addresses and use it to print the shadow stack backtrace as
> >> > described above.
> >> > ---
> >> >  gdb/doc/gdb.texinfo       | 19 ++++++++++++
> >> >  gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
> >> >  gdb/gdbarch-gen.h         | 15 +++++++++
> >> >  gdb/gdbarch.h             |  1 +
> >> >  gdb/gdbarch_components.py | 17 +++++++++++
> >> >  gdb/shadow-stack.c        | 64 +++++++++++++++++----------------------
> >> >  gdb/shadow-stack.h        | 37 ++++++++++++++++++++++
> >> >  7 files changed, 148 insertions(+), 37 deletions(-)
> >>
> >> Thanks.
> >>
> >> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
> >> > ebda4546b58..a0fde385a8e 100644
> >> > --- a/gdb/doc/gdb.texinfo
> >> > +++ b/gdb/doc/gdb.texinfo
> >> > @@ -8887,6 +8887,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
> >>                ^
> >> A cross-reference is missing here.
> >
> > Thanks will fix.
> >
> >> > +in a special format with bit 63 set.  For such shadow stack
> >> > +elements, the shadow stack frame just contains the level and the
> >> > +address on the shadow stack, as shown in the following example by
> frame 1:
> >> > +
> >> > +@smallexample
> >> > +@group
> >> > +(gdb) bt shadow 4
> >> > +#0  0x00007ffff7c54d90 in __restore_rt from /lib64/libc.so.6
> >> > +#1  0x80007ffff79fffd8
> >> > +#2  0x00007ffff7c54ce6 in __GI_raise at
> >> > +../sysdeps/posix/raise.c:27
> >> > +#3  0x000000000040115d in main at /tmp/amd64-shadow-stack-
> >> signal.c:32
> >> > +(More shadow stack frames follow...) @end group @end smallexample
> >>
> >> Would it make sense to show something like "<signal caught>", instead
> >> of a frame with only an address?
> >
> > Yeah, this is a good idea, I wondered about a similar thing actually
> > but wanted to discuss the general direction for handling those specific
> elements on the shadow stack first.
> > Maybe there are more options on other architectures that we have to
> consider.
> > Let's wait for more feedback on this, I added Thiago again in cc here.
> 
> Yes, I would like to show a "<signal handler called>" message in the
> corresponding shadow stack entry, as is done in the regular stack:
> 
>   (gdb) bt
>   #0  handler (signo=10) at aarch64-gcs-signal.c:59
>   #1  <signal handler called>
>   #2  __pthread_kill_implementation (threadid=281474842447584,
> signo=signo@entry=10, no_tid=no_tid@entry=0)
>       at ./nptl/pthread_kill.c:44
>   #3  0x0000fffff7e77670 in __pthread_kill_internal (signo=10,
> threadid=<optimized out>) at ./nptl/pthread_kill.c:78
>   #4  0x0000fffff7e2cb3c in __GI_raise (sig=10) at ../sysdeps/posix/raise.c:26
>   #5  0x0000aaaaaaaa0b24 in main () at aarch64-gcs-signal.c:96
> 
> Here is the corresponding GCS backtrace:
> 
>   (gdb) bt shadow
>   #0  0x0000fffff7ffa85c in __kernel_rt_sigreturn
>   #1  0x0000fffff7bff000 in ??
>   #2  0x0000fffff7e2cb3c in __GI_raise at ../sysdeps/posix/raise.c:27
>   #3  0x0000aaaaaaaa0b24 in main at aarch64-gcs-signal.c:99
> 
> In this example "<signal handler called>" message would replace the VDSO
> function name in entry #0. Also note that it's an actual return address but we
> still want to show a custom message for it.
> Finally, notice that there's GCS entry #1, which isn't a return address. It's a "cap
> record", which marks that the stack is currently not in use. When a signal is
> delivered, the kernel pushes two entries to the thread's GCS: first the cap
> record, then the address to the signal trampoline (__kernel_rt_sigreturn). For
> this entry, I'd like to show "<cap record>".
> One suggestion to handle that is to make do_print_shadow_stack_frame_info
> call a new hook gdbarch_print_shadow_stack_frame_info which would allow
> the architecture to print the shadow stack frame however it wants. If the hook
> returns false, then do_print_shadow_stack_frame_info would print the
> information itself, as it does in your patch. What do you think?

If possible, I'd like to keep it as generic as possible. The <signal handler called>
message is useful for CET, too. The CET equivalent for "cap record" should be "sigframe token".

|1...old SSP| - Pointer to old pre-signal ssp in sigframe token format
                (bit 63 set to 1)
(https://docs.kernel.org/next/x86/shstk.html)

In the commit message I described that "bt shadow" always prints the address:

"Similar to the ordinary backtrace command 'bt shadow' can be configured
using COUNT and the command line option -frame-info.  However, we always
print the address and the command is not affected by the setting
"print address" as well as the setting "print frame-info location-and-address".
Also we do not print the frame arguments."

Do you think we should align with the ordinary backtrace for the printing of shadow stack addresses ?
When implementing this I thought it would be nice to always print addresses, but now I am not so sure anymore.

Based on your suggestion we could print it as follows and replace the shadow stack addresses:

(gdb) bt shadow
#0  <signal handler called>
#1  <sigframe token >
#2  0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:27
#3  0x0000555555555175 in main at /tmp/amd64-shadow-stack-signal.c:30
#4  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:74
#5  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:128
#6  0x0000555555555085 in _start

For GCS we show "cap record" instead of "sigframe token".

In case the gdbarch hook "is_no_return_address" returns true, we could additionally return a string,
which should be displayed inside the <...>. So in our case this would be "sigframe token" or "cap record" .

What do you think?

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2025-11-12 17:28         ` Schimpe, Christina
@ 2025-11-16 18:39           ` Thiago Jung Bauermann
  2025-11-17 11:51             ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-11-16 18:39 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Eli Zaretskii, gdb-patches

Hello Christina,

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

>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Sonntag, 2. November 2025 22:20
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: Eli Zaretskii <eliz@gnu.org>; gdb-patches@sourceware.org
>> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow
>> stack backtrace elements.
>> 
>> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
>> 
>> >> -----Original Message-----
>> >> From: Eli Zaretskii <eliz@gnu.org>
>> >> Sent: Tuesday, September 23, 2025 1:50 PM
>> >> To: Schimpe, Christina <christina.schimpe@intel.com>
>> >> Cc: gdb-patches@sourceware.org
>> >> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish
>> >> shadow stack backtrace elements.
>> >>
>> >> > From: Christina Schimpe <christina.schimpe@intel.com>
>> >> > Date: Tue, 23 Sep 2025 11:18:40 +0000
>> >> >
>> >> > On x86 with CET there can be elements on the shadow stack which are
>> >> > not return addresses.  In this case, we just want to print the
>> >> > element itself in the shadow stack backtrace, but no further information.
>> >> >
>> >> > Provide a gdbarch hook to distinguish between return and non-return
>> >> > addresses and use it to print the shadow stack backtrace as
>> >> > described above.
>> >> > ---
>> >> >  gdb/doc/gdb.texinfo       | 19 ++++++++++++
>> >> >  gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
>> >> >  gdb/gdbarch-gen.h         | 15 +++++++++
>> >> >  gdb/gdbarch.h             |  1 +
>> >> >  gdb/gdbarch_components.py | 17 +++++++++++
>> >> >  gdb/shadow-stack.c        | 64 +++++++++++++++++----------------------
>> >> >  gdb/shadow-stack.h        | 37 ++++++++++++++++++++++
>> >> >  7 files changed, 148 insertions(+), 37 deletions(-)
>> >>
>> >> Thanks.
>> >>
>> >> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
>> >> > ebda4546b58..a0fde385a8e 100644
>> >> > --- a/gdb/doc/gdb.texinfo
>> >> > +++ b/gdb/doc/gdb.texinfo
>> >> > @@ -8887,6 +8887,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
>> >>                ^
>> >> A cross-reference is missing here.
>> >
>> > Thanks will fix.
>> >
>> >> > +in a special format with bit 63 set.  For such shadow stack
>> >> > +elements, the shadow stack frame just contains the level and the
>> >> > +address on the shadow stack, as shown in the following example by frame 1:
>> >> > +
>> >> > +@smallexample
>> >> > +@group
>> >> > +(gdb) bt shadow 4
>> >> > +#0  0x00007ffff7c54d90 in __restore_rt from /lib64/libc.so.6
>> >> > +#1  0x80007ffff79fffd8
>> >> > +#2  0x00007ffff7c54ce6 in __GI_raise at
>> >> > +../sysdeps/posix/raise.c:27
>> >> > +#3  0x000000000040115d in main at /tmp/amd64-shadow-stack-signal.c:32
>> >> > +(More shadow stack frames follow...) @end group @end smallexample
>> >>
>> >> Would it make sense to show something like "<signal caught>", instead
>> >> of a frame with only an address?
>> >
>> > Yeah, this is a good idea, I wondered about a similar thing actually
>> > but wanted to discuss the general direction for handling those specific
>> > elements on the shadow stack first.
>> > Maybe there are more options on other architectures that we have to
>> > consider.
>> > Let's wait for more feedback on this, I added Thiago again in cc here.
>> 
>> Yes, I would like to show a "<signal handler called>" message in the
>> corresponding shadow stack entry, as is done in the regular stack:
>> 
>>   (gdb) bt
>>   #0  handler (signo=10) at aarch64-gcs-signal.c:59
>>   #1  <signal handler called>
>>   #2  __pthread_kill_implementation (threadid=281474842447584,
>> signo=signo@entry=10, no_tid=no_tid@entry=0)
>>       at ./nptl/pthread_kill.c:44
>>   #3  0x0000fffff7e77670 in __pthread_kill_internal (signo=10,
>> threadid=<optimized out>) at ./nptl/pthread_kill.c:78
>>   #4  0x0000fffff7e2cb3c in __GI_raise (sig=10) at ../sysdeps/posix/raise.c:26
>>   #5  0x0000aaaaaaaa0b24 in main () at aarch64-gcs-signal.c:96
>> 
>> Here is the corresponding GCS backtrace:
>> 
>>   (gdb) bt shadow
>>   #0  0x0000fffff7ffa85c in __kernel_rt_sigreturn
>>   #1  0x0000fffff7bff000 in ??
>>   #2  0x0000fffff7e2cb3c in __GI_raise at ../sysdeps/posix/raise.c:27
>>   #3  0x0000aaaaaaaa0b24 in main at aarch64-gcs-signal.c:99
>> 
>> In this example "<signal handler called>" message would replace the VDSO
>> function name in entry #0. Also note that it's an actual return address but we
>> still want to show a custom message for it.
>> Finally, notice that there's GCS entry #1, which isn't a return address. It's a "cap
>> record", which marks that the stack is currently not in use. When a signal is
>> delivered, the kernel pushes two entries to the thread's GCS: first the cap
>> record, then the address to the signal trampoline (__kernel_rt_sigreturn). For
>> this entry, I'd like to show "<cap record>".
>> One suggestion to handle that is to make do_print_shadow_stack_frame_info
>> call a new hook gdbarch_print_shadow_stack_frame_info which would allow
>> the architecture to print the shadow stack frame however it wants. If the hook
>> returns false, then do_print_shadow_stack_frame_info would print the
>> information itself, as it does in your patch. What do you think?
>
> If possible, I'd like to keep it as generic as possible. The <signal handler called>
> message is useful for CET, too. The CET equivalent for "cap record" should be "sigframe
> token".
>
> |1...old SSP| - Pointer to old pre-signal ssp in sigframe token format
>                 (bit 63 set to 1)
> (https://docs.kernel.org/next/x86/shstk.html)

Agreed, it should be as generic as possible.

> In the commit message I described that "bt shadow" always prints the address:
>
> "Similar to the ordinary backtrace command 'bt shadow' can be configured
> using COUNT and the command line option -frame-info.  However, we always
> print the address and the command is not affected by the setting
> "print address" as well as the setting "print frame-info location-and-address".
> Also we do not print the frame arguments."
>
> Do you think we should align with the ordinary backtrace for the printing of shadow stack
> addresses ?
> When implementing this I thought it would be nice to always print addresses, but now I am
> not so sure anymore.

I understand why it makes sense to ignore the "print address" setting:
essentially the only things the shadow stack contains are return
addresses, and we're not going to print them? My inclination is to agree
with always printing them.

But thinking a bit more about it, the frame information that GDB prints
is good even if the address isn't printed along with it so I think it's
better to respect the setting. Also it's good to be consistent with the
regular backtrace output.

> Based on your suggestion we could print it as follows and replace the shadow stack
> addresses:
>
> (gdb) bt shadow
> #0  <signal handler called>
> #1  <sigframe token >
> #2  0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:27
> #3  0x0000555555555175 in main at /tmp/amd64-shadow-stack-signal.c:30
> #4 0x00007ffff7c2a1ca in __libc_start_call_main at
> ../sysdeps/nptl/libc_start_call_main.h:74
> #5  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:128
> #6  0x0000555555555085 in _start
>
> For GCS we show "cap record" instead of "sigframe token".

I agree. I like the output above.

My only comment is that there a also the "set backtrace past-main" and
"set backtrace past-entry" options, which "bt shadow" should also
respect.  So if the former setting is on, the output above should stop
at frame #3.

> In case the gdbarch hook "is_no_return_address" returns true, we could additionally return
> a string,
> which should be displayed inside the <...>. So in our case this would be "sigframe token"
> or "cap record" .
>
> What do you think?

I think that is a good idea, and should be enough for GCS.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer.
  2025-10-31  1:31   ` Thiago Jung Bauermann
@ 2025-11-17 11:18     ` Schimpe, Christina
  2025-11-26  4:19       ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-11-17 11:18 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

Hi Thiago,

Thanks a lot for this detailed review!
I applied most of your comments, please find my feedback to your review below.

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Friday, 31 October 2025 02:32
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 1/9] gdb: Generalize handling of the shadow stack
> pointer.
> 
> Christina Schimpe <christina.schimpe@intel.com> writes:
> 
> > 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 commit a subcommmand "backtrace shadow" of the ordinary
> 
> Too mmany m's in "subcommand".

Fixed.

> 
> > backtrace command will be added 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.
> 
> <snip>
> 
> > diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> > 500ac77d75a..95af82c2632 100644
> > --- a/gdb/aarch64-tdep.c
> > +++ b/gdb/aarch64-tdep.c
> 
> As mentioned before, this change is also needed in aarch64-tdep.c to make
> this patch series work for AArch64:
> 
> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> 95af82c26327..9e866fc319d4 100644
> --- a/gdb/aarch64-tdep.c
> +++ b/gdb/aarch64-tdep.c
> @@ -4780,6 +4780,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);
> 
> +  if (tdep->has_gcs ())
> +    /* AArch64's shadow stack pointer is the GCSPR.  */
> +    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
> +

Added.

>    /* ABI */
>    set_gdbarch_short_bit (gdbarch, 16);
>    set_gdbarch_int_bit (gdbarch, 32);
> 
> > -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)
> 
> IIUC, the '<=' comparison above isn't preserved by this patch. This function is
> replaced by dwarf2_prev_ssp, which uses
> gdbarch_address_in_shadow_stack_memory_range for this if condition,
> whose comparison in find_addr_mem_range is:
> 
>       bool addr_in_mem_range
>         = (addr >= map.start_address && addr < map.end_address);
> 
> Is this intended?

Arg, thanks for catching that!

I think I missed that because I introduced a typo/bug in the call

	      || gdbarch_address_in_shadow_stack_memory_range (gdbarch,
							       ssp,
							       &range))

which made the unwinding work properly in case of amd64.
However, the proper fix should be to pass new_ssp to gdbarch_address_in_shadow_stack_memory_range
instead, and to implement gdbarch_top_addr_empty_shadow_stack also for amd64.

Does that make sense?

> > -	    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;
> > -}
> 
> <snip>
> 
> > diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c new file mode
> > 100644 index 00000000000..d153d5fc846
> > --- /dev/null
> > +++ b/gdb/shadow-stack.c
> > @@ -0,0 +1,167 @@
> > +/* Manage a shadow stack pointer for GDB, the GNU debugger.
> > +
> > +   Copyright (C) 2024-2025 Free Software Foundation, Inc.
> 
> Should this really start at 2024? According to Andrew Burgess¹:

Yes, 2024 is correct in this case since our gdb-oneapi supported bt shadow since 2024. 

> > The start date should be when the patches were first posted to the
> > list, or otherwise made publicly available (e.g. Intel specific GDB release?).
> > The end date should be updated to 2025.
> 
> Same question for other files created by this patch series.
> 
> > +   This file is part of GDB.
> > +
> > +   This program is free software; you can redistribute it and/or modify
> > +   it under the terms of the GNU General Public License as published by
> > +   the Free Software Foundation; either version 3 of the License, or
> > +   (at your option) any later version.
> > +
> > +   This program is distributed in the hope that it will be useful,
> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +   GNU General Public License for more details.
> > +
> > +   You should have received a copy of the GNU General Public License
> > +   along with this program.  If not, see
> > + <http://www.gnu.org/licenses/>.  */
> > +
> > +#include "defs.h"
> > +#include "arch-utils.h"
> > +#include "gdbcore.h"
> > +#include "extract-store-integer.h"
> > +#include "frame.h"
> > +#include "frame-unwind.h"
> > +#include "shadow-stack.h"
> > +
> > +enum class ssp_update_direction
> > +{
> > +  /* Update ssp towards the bottom of the shadow stack.  */
> > +  bottom = 0,
> > +
> > +  /* Update ssp towards the top of the shadow stack.  */
> > +  top
> > +};
> 
> I find the bottom/top nomenclature confusing, especially because it's
> supposed to mean the same thing whether the stack grows up or down. In
> my mind, if the stack grow down then top means "oldest element", but if the
> stack grows up, then top means "newest element".
> But in this patch it seems that top means "newest element" regardless of the
> direction of stack growth.

Yes, that was my understanding. So independent in which direction a shadow stack
grows based on the architecture/OS, top always means newest element.  But I think
it is not a problem to take one of your suggestions.

> I would suggest changing the enum names above to something that's not
> related to the vertical axis, so that their meaning will be clear regardless of
> which direction the stack grows. A few suggestions:
> shrink/grow, older/younger, outer/inner.

I'd take outer/inner and describe it as follows:

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
};

Is that understandable ?

> > +/* Return a new shadow stack pointer which is incremented or
> decremented
> > +   by COUNT elements 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::bottom)
> > +		      : (direction == ssp_update_direction::top);
> 
> All the parentheses above are superfluous and can be removed.

Ok, fixed.

> > +  CORE_ADDR new_ssp;
> 
> This variable is unused and can be removed. Because by default GDB build
> with -Werror, this patch breaks the build with an "unused variable"
> error until patch 6 which removes this variable.

Arg, I missed that, because it's also fixed in a later patch.
I now removed it also from this patch.

> > +  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 (gdbarch *gdbarch, regcache *regcache,
> 
> There's no need for a gdbarch argument. You can get it from the regcache.

Fixed.

> > +			const CORE_ADDR new_addr)
> > +{
> > +  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::top);
> > +
> > +  /* If NEW_SSP does not point to shadow stack memory, we assume the
> stack
> > +     is full.  */
> > +  std::pair<CORE_ADDR, CORE_ADDR> range;
> > +  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> > +						     new_ssp,
> > +						     &range))
> 
> Range isn't really needed by this function. I suggest changing
> gdbarch_address_in_shadow_stack_memory_range to allow for it to be
> nullptr and then pass nullptr here.

I agree, fixed.

> Also, the line above fits in 80 columns and doesn't need to be broken, even if
> "&range" is changed to "nullptr".

It is more than 80 columns for me.

> 
> > +    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.  */
> 
> s/8 bytes/element size/

Fixed.

> 
> > +  const int element_size
> > +    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
> > +
> > +  const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> > +
> > +  write_memory_unsigned_integer (new_ssp, element_size, byte_order,
> > +				 (ULONGEST) new_addr);
> > +
> > +  regcache_raw_write_unsigned (regcache,
> > +			       gdbarch_ssp_regnum (gdbarch),
> > +			       new_ssp);
> 
> The line above fits in 80 columns and doesn't need to be broken.

I count 81 columns and there is also a soft limit of 74 characters:

https://sourceware.org/legacy-ml/gdb-patches/2014-01/msg00216.html

So I'll keep it as is, if that's fine for you.

> 
> > +}
> > +
> > +/* 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))
> 
> This line fits in 80 columns and doesn't need to be broken.

In this case I'll take your suggestion, since it looks much nicer unbroken. 😊

> 
> > +	{
> > +	  /* 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::bottom);
> > +
> > +	  /* On x86, if NEW_SSP points to the end of RANGE, it indicates
> > +	     that NEW_SSP is valid, but the shadow stack is empty.  In
> > +	     contrast, on ARM's Guarded Control Stack, if NEW_SSP points
> > +	     to the end of RANGE, it means that the shadow stack pointer
> > +	     is invalid.  */
> > +	  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);
> > +
> > +	  /* Check whether the new SSP is valid.  Depending on the
> > +	     architecture, this may rely on both
> > +	     IS_TOP_ADDR_EMPTY_SHADOW_STACK and the return value of
> > +	     gdbarch_address_in_shadow_stack_memory_range, or on the
> > +	     latter only.  */
> > +	  if (is_top_addr_empty_shadow_stack
> 
> Considering that, as previously mentioned,
> gdbarch_address_in_shadow_stack_memory_range uses '<' rather than '<='
> in the memory range check, AArch64 doesn't need
> gdbarch_top_addr_empty_shadow_stack.

Agree, but for amd64 I need it... (as described before).

> 
> The way this if condition is written, I think it's x86_64 that would need to
> provide an gdbarch_top_addr_empty_shadow_stack implementation.
> I'm surprised it doesn't.

As described before, I missed to pass new_ssp instead of ssp, which made this
code work for amd64.  But it's not correct, and I need
gdbarch_top_addr_empty_shadow_stack.
Thanks again for catching that!

> > +	      || gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> > +							       ssp,
> 
> Shouldn't this argument be new_ssp?

Already discussed before.

> 
> > +							       &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;
> > +
> 
> Spurious empty line added here.

Fixed.

> > +}
> > diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h new file mode
> > 100644 index 00000000000..5c3ba80974e
> > --- /dev/null
> > +++ b/gdb/shadow-stack.h
> > @@ -0,0 +1,39 @@
> > +/* Definitions to manage a shadow stack pointer for GDB, the GNU
> debugger.
> > +
> > +   Copyright (C) 2024-2025 Free Software Foundation, Inc.
> > +
> > +   This file is part of GDB.
> > +
> > +   This program is free software; you can redistribute it and/or
> > + modify
> > +
> > +   it under the terms of the GNU General Public License as published by
> > +   the Free Software Foundation; either version 3 of the License, or
> > +   (at your option) any later version.
> > +
> > +   This program is distributed in the hope that it will be useful,
> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +   GNU General Public License for more details.
> > +
> > +   You should have received a copy of the GNU General Public License
> > +   along with this program.  If not, see
> > + <http://www.gnu.org/licenses/>.  */
> > +
> > +#ifndef GDB_SHADOW_STACK_H
> > +#define GDB_SHADOW_STACK_H
> > +
> > +/* If shadow stack is enabled, push the address NEW_ADDR on the
> shadow
> > +   stack and update the shadow stack pointer accordingly.  */
> > +
> > +void shadow_stack_push (gdbarch *gdbarch, regcache *regcache,
> 
> Recently, the project has been trying to make the header files contain all the
> headers and definitions that they need, for the benefit of IDE and language
> server users, so that these tools don't emit spurious errors when showing a
> header file.

Ah, ok I wasn't aware. Do you have a link for that ? I think I cannot follow 100 %.

> In this case, clangd is complaining here that regcache is unknown. There's no
> need to include regcache.h just for it, just adding a "class regcache" forward
> declaration at the beginning of the file is enough.
> 
> > +			const CORE_ADDR new_addr);

Ok, added!

> > +
> > +/* 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,
> 
> The space after "value *" should be removed.

Fixed.

> 
> > +			 void **this_cache, int regnum);
> > +
> > +#endif /* GDB_SHADOW_STACK_H */
> --
> Thiago
> 
> ¹ https://inbox.sourceware.org/gdb-patches/87ldo6c84t.fsf@redhat.com/


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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2025-11-16 18:39           ` Thiago Jung Bauermann
@ 2025-11-17 11:51             ` Schimpe, Christina
  0 siblings, 0 replies; 67+ messages in thread
From: Schimpe, Christina @ 2025-11-17 11:51 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: Eli Zaretskii, gdb-patches

Hi Thiago,

Thank you for the feedback.
There is only one open left for the implementation of the settings past-main/past-entry.
Please find my comment below.

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Sonntag, 16. November 2025 19:39
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: Eli Zaretskii <eliz@gnu.org>; gdb-patches@sourceware.org
> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow
> stack backtrace elements.
> 
> Hello Christina,
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> >> -----Original Message-----
> >> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> Sent: Sonntag, 2. November 2025 22:20
> >> To: Schimpe, Christina <christina.schimpe@intel.com>
> >> Cc: Eli Zaretskii <eliz@gnu.org>; gdb-patches@sourceware.org
> >> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish
> >> shadow stack backtrace elements.
> >>
> >> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> >>
> >> >> -----Original Message-----
> >> >> From: Eli Zaretskii <eliz@gnu.org>
> >> >> Sent: Tuesday, September 23, 2025 1:50 PM
> >> >> To: Schimpe, Christina <christina.schimpe@intel.com>
> >> >> Cc: gdb-patches@sourceware.org
> >> >> Subject: Re: [PATCH 7/9] gdb: Provide gdbarch hook to distinguish
> >> >> shadow stack backtrace elements.
> >> >>
> >> >> > From: Christina Schimpe <christina.schimpe@intel.com>
> >> >> > Date: Tue, 23 Sep 2025 11:18:40 +0000
> >> >> >
> >> >> > On x86 with CET there can be elements on the shadow stack which
> >> >> > are not return addresses.  In this case, we just want to print
> >> >> > the element itself in the shadow stack backtrace, but no further
> information.
> >> >> >
> >> >> > Provide a gdbarch hook to distinguish between return and
> >> >> > non-return addresses and use it to print the shadow stack
> >> >> > backtrace as described above.
> >> >> > ---
> >> >> >  gdb/doc/gdb.texinfo       | 19 ++++++++++++
> >> >> >  gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
> >> >> >  gdb/gdbarch-gen.h         | 15 +++++++++
> >> >> >  gdb/gdbarch.h             |  1 +
> >> >> >  gdb/gdbarch_components.py | 17 +++++++++++
> >> >> >  gdb/shadow-stack.c        | 64 +++++++++++++++++----------------------
> >> >> >  gdb/shadow-stack.h        | 37 ++++++++++++++++++++++
> >> >> >  7 files changed, 148 insertions(+), 37 deletions(-)
> >> >>
> >> >> Thanks.
> >> >>
> >> >> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
> >> >> > ebda4546b58..a0fde385a8e 100644
> >> >> > --- a/gdb/doc/gdb.texinfo
> >> >> > +++ b/gdb/doc/gdb.texinfo
> >> >> > @@ -8887,6 +8887,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
> >> >>                ^
> >> >> A cross-reference is missing here.
> >> >
> >> > Thanks will fix.
> >> >
> >> >> > +in a special format with bit 63 set.  For such shadow stack
> >> >> > +elements, the shadow stack frame just contains the level and
> >> >> > +the address on the shadow stack, as shown in the following example
> by frame 1:
> >> >> > +
> >> >> > +@smallexample
> >> >> > +@group
> >> >> > +(gdb) bt shadow 4
> >> >> > +#0  0x00007ffff7c54d90 in __restore_rt from /lib64/libc.so.6
> >> >> > +#1  0x80007ffff79fffd8
> >> >> > +#2  0x00007ffff7c54ce6 in __GI_raise at
> >> >> > +../sysdeps/posix/raise.c:27
> >> >> > +#3  0x000000000040115d in main at
> >> >> > +/tmp/amd64-shadow-stack-signal.c:32
> >> >> > +(More shadow stack frames follow...) @end group @end
> >> >> > +smallexample
> >> >>
> >> >> Would it make sense to show something like "<signal caught>",
> >> >> instead of a frame with only an address?
> >> >
> >> > Yeah, this is a good idea, I wondered about a similar thing
> >> > actually but wanted to discuss the general direction for handling
> >> > those specific elements on the shadow stack first.
> >> > Maybe there are more options on other architectures that we have to
> >> > consider.
> >> > Let's wait for more feedback on this, I added Thiago again in cc here.
> >>
> >> Yes, I would like to show a "<signal handler called>" message in the
> >> corresponding shadow stack entry, as is done in the regular stack:
> >>
> >>   (gdb) bt
> >>   #0  handler (signo=10) at aarch64-gcs-signal.c:59
> >>   #1  <signal handler called>
> >>   #2  __pthread_kill_implementation (threadid=281474842447584,
> >> signo=signo@entry=10, no_tid=no_tid@entry=0)
> >>       at ./nptl/pthread_kill.c:44
> >>   #3  0x0000fffff7e77670 in __pthread_kill_internal (signo=10,
> >> threadid=<optimized out>) at ./nptl/pthread_kill.c:78
> >>   #4  0x0000fffff7e2cb3c in __GI_raise (sig=10) at
> ../sysdeps/posix/raise.c:26
> >>   #5  0x0000aaaaaaaa0b24 in main () at aarch64-gcs-signal.c:96
> >>
> >> Here is the corresponding GCS backtrace:
> >>
> >>   (gdb) bt shadow
> >>   #0  0x0000fffff7ffa85c in __kernel_rt_sigreturn
> >>   #1  0x0000fffff7bff000 in ??
> >>   #2  0x0000fffff7e2cb3c in __GI_raise at ../sysdeps/posix/raise.c:27
> >>   #3  0x0000aaaaaaaa0b24 in main at aarch64-gcs-signal.c:99
> >>
> >> In this example "<signal handler called>" message would replace the
> >> VDSO function name in entry #0. Also note that it's an actual return
> >> address but we still want to show a custom message for it.
> >> Finally, notice that there's GCS entry #1, which isn't a return
> >> address. It's a "cap record", which marks that the stack is currently
> >> not in use. When a signal is delivered, the kernel pushes two entries
> >> to the thread's GCS: first the cap record, then the address to the
> >> signal trampoline (__kernel_rt_sigreturn). For this entry, I'd like to show
> "<cap record>".
> >> One suggestion to handle that is to make
> >> do_print_shadow_stack_frame_info call a new hook
> >> gdbarch_print_shadow_stack_frame_info which would allow the
> >> architecture to print the shadow stack frame however it wants. If the
> >> hook returns false, then do_print_shadow_stack_frame_info would print
> the information itself, as it does in your patch. What do you think?
> >
> > If possible, I'd like to keep it as generic as possible. The <signal
> > handler called> message is useful for CET, too. The CET equivalent for
> > "cap record" should be "sigframe token".
> >
> > |1...old SSP| - Pointer to old pre-signal ssp in sigframe token format
> >                 (bit 63 set to 1)
> > (https://docs.kernel.org/next/x86/shstk.html)
> 
> Agreed, it should be as generic as possible.
> 
> > In the commit message I described that "bt shadow" always prints the
> address:
> >
> > "Similar to the ordinary backtrace command 'bt shadow' can be
> > configured using COUNT and the command line option -frame-info.
> > However, we always print the address and the command is not affected
> > by the setting "print address" as well as the setting "print frame-info
> location-and-address".
> > Also we do not print the frame arguments."
> >
> > Do you think we should align with the ordinary backtrace for the
> > printing of shadow stack addresses ?
> > When implementing this I thought it would be nice to always print
> > addresses, but now I am not so sure anymore.
> 
> I understand why it makes sense to ignore the "print address" setting:
> essentially the only things the shadow stack contains are return addresses,
> and we're not going to print them? My inclination is to agree with always
> printing them.
> 
> But thinking a bit more about it, the frame information that GDB prints is
> good even if the address isn't printed along with it so I think it's better to
> respect the setting. Also it's good to be consistent with the regular backtrace
> output.

Ok, so for the v2 I'll change that and align more with he regular backtrace for
address printing.

> > Based on your suggestion we could print it as follows and replace the
> > shadow stack
> > addresses:
> >
> > (gdb) bt shadow
> > #0  <signal handler called>
> > #1  <sigframe token >
> > #2  0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:27
> > #3  0x0000555555555175 in main at /tmp/amd64-shadow-stack-
> signal.c:30
> > #4 0x00007ffff7c2a1ca in __libc_start_call_main at
> > ../sysdeps/nptl/libc_start_call_main.h:74
> > #5  0x00007ffff7c2a28b in __libc_start_main_impl at
> > ../csu/libc-start.c:128
> > #6  0x0000555555555085 in _start
> >
> > For GCS we show "cap record" instead of "sigframe token".
> 
> I agree. I like the output above.
> 
> My only comment is that there a also the "set backtrace past-main" and "set
> backtrace past-entry" options, which "bt shadow" should also respect.  So if
> the former setting is on, the output above should stop at frame #3.

Ah those settings, ... I tried to add past-main/past-entry but stopped at some point,
since it turned out to be complicated - but I don't remember the exact reasons.
Do you think I missed something and adding those settings should be straight-forward?

I then decided to first post the basic functionality of "bt shadow" without support
for past-main/past-entry. Do you think this is a problem ?

In any case, I should make that clearer in the commit message.

> > In case the gdbarch hook "is_no_return_address" returns true, we could
> > additionally return a string, which should be displayed inside the
> > <...>. So in our case this would be "sigframe token"
> > or "cap record" .
> >
> > What do you think?
> 
> I think that is a good idea, and should be enough for GCS.
> 
> --
> Thiago

Ok, will go ahead with that approach for the v2.

Christina

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-10-31  4:02   ` Thiago Jung Bauermann
@ 2025-11-17 20:14     ` Schimpe, Christina
  2025-11-26  4:07       ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-11-17 20:14 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

Hi Thiago,

Thanks for taking the time to provide this review—I really appreciate your detailed input!

I’ve implemented part of your feedback, and there are still a few open points I’d like to clarify.
I hope I didn’t miss any of your comments, as the review has become quite comprehensive.

Please find my responses and open questions below. Looking forward to your thoughts!

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Freitag, 31. Oktober 2025 05:02
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> Christina Schimpe <christina.schimpe@intel.com> writes:
> 
> > Add a subcommand 'bt shadow' for the ordinary backtrace command
> which
> > prints the shadow stack backtrace.
> > Similar to the ordinary backtrace command 'bt shadow' can be
> > configured using COUNT and the command line option -frame-info.
> > However, we always print the address and the command is not affected
> > by the setting "print address" as well as the setting "print frame-info
> location-and-address".
> > Also we do not print the frame arguments.
> >
> > Usage: backtrace|bt shadow [OPTION]... [COUNT | -COUNT]
> >
> > Help output:
> > ~~
> > (gdb) help bt shadow
> > Print backtrace of all shadow stack frames, or innermost COUNT frames.
> > Usage: backtrace shadow [OPTION]... [COUNT | -COUNT]
> >
> > Options:
> >   -frame-info auto|source-line|location|source-and-location|location-and-
> address|short-location
> >     Set printing of shadow stack frame information.
> >
> > With a negative COUNT, print outermost -COUNT elements.
> > ~~
> 
> There's another thread discussion whether to use "bt -shadow" instead,
> and/or "shadow-stack backtrace" so I won't comment on this here.

Yes, I will go ahead with "bt -shadow" for the v2. 

> > Example for the output of 'bt shadow' on amd64 linux:
> > ~~
> > (gdb) bt shadow
> > /#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
> > /#1  0x000000000040112f in main at amd64-shadow-stack.c:21
> > /#2  0x00007ffff7c3fe70 in __libc_start_call_main at
> > ../sysdeps/nptl/libc_start_call_main.h:58
> > /#3  0x00007ffff7c3ff20 in __libc_start_main_impl at
> > ../csu/libc-start.c:128
> > /#4  0x0000000000401045 in _start
> > ~~
> 
> Here's an example output in my aarch64-linux test VM:
> 
> (gdb) bt shadow
> #0  0x0000aaaaaaaa08ac in call2 at aarch64-gcs-return.c:65
> #1  0x0000aaaaaaaa08c0 in call1 at aarch64-gcs-return.c:71
> #2  0x0000aaaaaaaa09d4 in main at aarch64-gcs-return.c:110
> #3  0x0000000000000000 in ??
> 
> And here is the regular backtrace at the same point:
> 
> (gdb) bt
> #0  call3 () at aarch64-gcs-return.c:58
> #1  0x0000aaaaaaaa08ac in call2 () at aarch64-gcs-return.c:64
> #2  0x0000aaaaaaaa08c0 in call1 () at aarch64-gcs-return.c:70
> #3  0x0000aaaaaaaa09d4 in main () at aarch64-gcs-return.c:107
> 
> I have a few comments on this output, at least as it is appears for GCS. I'm
> assuming x86 shadow stack output is similar:
> 
> 1. The first thing that I notice is that the number of the frames don't match
> between the regular stack and the shadow stack: frame 0 in the regular stack
> doesn't exist in the shadow stack, regular frame 1 is shadow frame 0, and so
> on.
> 
> I think this is confusing. It makes sense that frame 0 doesn't appear in the
> shadow stack backtrace because there really isn't an entry for it in the
> shadow stack, but I think the shadow entries should start with number 1, to
> be consistent.

Mh, it's probably better to align, I agree.
I'll go with starting frame #1 in the v2, and add a comment about that in the
commit message.

> 2. The line numbers corresponding to the return addresses don't match
> between the regular backtrace and the shadow backtrace, even though the
> return addresses are the same. I would say this is a bug. Perhaps there can be
> a test making sure they match, if it's not too complicated?

The reason for that is explained in a comment in do_print_shadow_stack_frame_info:

  /* In contrast to find_frame_sal which is used for the ordinary backtrace
     command, we always want to print the line that is actually referred
     to by the address in the linetable.  That's why we always pass 0 here
     as second argument.  */

Do you think we should align this with the behavior of the ordinary backtrace command
here as well?
I’m not entirely sure if that approach might be too straightforward, since we can’t simply
call the get_frame_address_in_block function as the ordinary bt command does when
invoking it from find_frame_sal.

> 3. One difference between my GCS test programs and your x86 test programs
> is the point at which the shadow stack is enabled. In your case, it appears to
> be done by glibc or the dynamic loader, very early in the process execution.
> For the AArch64 testcases I'm doing it explicitly in main itself, so that the test
> programs don't have to depend on a supported glibc or loader. All this to say
> that there is no GCS entry before main, yet one appears in the output above
> with a zeroed return address. I think this can be solved by stopping the
> backtrace loop if gdbarch_top_addr_empty_shadow_stack returns true in
> backtrace_shadow_command. I have a suggestion about this further down.

Yes, that makes sense to me.

> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
> > e8515883820..ebda4546b58 100644
> > --- a/gdb/doc/gdb.texinfo
> > +++ b/gdb/doc/gdb.texinfo
> > @@ -8840,6 +8840,53 @@ Display an absolute filename.
> >  Show the current way to display filenames.
> >  @end table
> >
> > +@subsection Shadow stack backtrace
> > +@value{GDBN} provides a subcommand of the backtrace command to
> print
> > +the shadow stack backtrace.
> > +
> > +@anchor{shadowstack-backtrace-command}
> > +@kindex backtrace shadow
> > +@kindex bt shadow @r{(@code{backtrace shadow})} To print a backtrace
> > +of the entire shadow stack, use the @code{backtrace shadow} command,
> > +or its alias @code{bt shadow}.  This command will print 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 (@xref{CET}) on x86.
> 
> Can you also add:
> 
> ... and the Guarded Control Stack feature (@xref{GCS}) on AArch64.
> 
> which would also require a @node entry here:

Yes, will add.

> @@ -27112,6 +27113,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}.
> 
> +@node GCS
>  @subsubsection AArch64 Guarded Control Stack  @cindex Guarded Control
> Stack, AArch64  @cindex GCS, AArch64
> 
> <snip>
> 
> > +/* 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_frame_sal).  Otherwise, we would have some PC range if the
> 
> In the case of stack.c:frame_show_address, SAL comes from find_frame_sal,
> but in this case it comes from find_pc_line, right?

Yes, it comes from find_sal_for_pc (when rebased on latest master, find_pc_line was renamed).

> Does the comment still apply in this case?

Good question, I remember I stumbled over this a couple of times when implementing this.
In any case, the comment needs to be corrected to "(SAL comes from find_sal_for_pc)."
If we need this check, I am not sure. In my experiments with inlined functions I could
not reproduce that scenario. But I kept it, to be safe.

> > +     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; }
> > +
> > +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
> > +    (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range);
> > +
> > +  /* The shadow stack pointer.  */
> > +  CORE_ADDR ssp;
> 
> Please add an empty line between each member variable and the next
> variable's documentation comment.

Fixed.

> > +  /* The value of the shadow stack at SSP.  */
> > +  CORE_ADDR value;
> > +  /* The level of the element on the shadow stack.  */
> > +  unsigned long level;
> > +  /* 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; };
> > +
> > +static
> > +gdb::unique_xmalloc_ptr<char> find_pc_funname (CORE_ADDR pc)
> 
> The return type should be together with the static.
> Also, this function needs a documentation comment.

Fixed.

> > +{
> > +  symbol *func = find_pc_function (pc);
> > +  if (func)
> > +    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.reset (xstrdup (msymbol.minsym->print_name ()));
> 
> Just repeating Tom's comment from patch 4:
> 
> This should use make_unique_xstrdup and plain assignment.

Fixed.

> > +
> > +  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, gdbarch *gdbarch,
> > +   const shadow_stack_print_options &print_options,
> > +   const shadow_stack_frame_info &frame, print_what print_what) {
> > +  if (print_options.print_frame_info != print_frame_info_auto)
> > +    {
> > +      /* Use the specific frame information desired by the user.  */
> > +      print_what
> > +	= *print_frame_info_to_print_what (print_options.print_frame_info);
> > +    }
> > +
> > +  /* In contrast to find_frame_sal which is used for the ordinary backtrace
> > +     command, we always want to print the line that is actually referred
> > +     to by the address in the linetable.  That's why we always pass 0 here
> > +     as second argument.  */
> > +  symtab_and_line sal = find_pc_line (frame.value, 0);
> > +
> > +  if (should_print_location (print_what) || sal.symtab == nullptr)
> > +    {
> > +      gdb::unique_xmalloc_ptr<char> funname
> > +	= find_pc_funname (frame.value);
> 
> This line fits in 80 columns and doesn't need to be broken.

Even though there is this soft limit I agree it looks better unbroken.
I changed this.

> > +      { /* Extra scope to print frame tuple.  */
> > +	ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame");
> > +
> > +	annotate_shadowstack_frame_begin (frame.level, gdbarch,
> > +					  frame.value);
> > +
> > +	uiout->text ("#");
> > +	uiout->field_fmt_signed (2, ui_left, "level", frame.level);
> > +
> > +	annotate_shadowstack_frame_address ();
> > +
> > +	/* 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 (gdbarch);
> > +
> > +	uiout->field_string
> > +	  ("addr", hex_string_custom (frame.value, element_size * 2),
> > +	   address_style.style ());
> > +
> > +	annotate_shadowstack_frame_address_end ();
> > +
> > +	uiout->text (" in ");
> > +	print_funname (uiout, funname, true);
> > +
> > +	if (print_what != SHORT_LOCATION && sal.symtab != nullptr)
> > +	  print_filename (uiout, sal, true);
> > +
> > +	if (print_what != SHORT_LOCATION
> > +	    && (funname == nullptr || sal.symtab == nullptr)
> > +	    && sal.pspace != nullptr)
> > +	  {
> > +	    const char *lib = solib_name_from_address (sal.pspace,
> > +						       frame.value);
> 
> This line fits in 80 columns and doesn't need to be broken.

I count 81 characters.

> > +	    if (lib != nullptr)
> > +	      print_lib (uiout, lib, true);
> > +	  }
> > +      } /* Extra scope to print frame tuple.  */
> > +
> > +      uiout->text ("\n");
> > +    }
> > +
> > +  if (print_what == SRC_LINE || print_what == SRC_AND_LOC)
> > +    {
> > +      int mid_statement = pc_in_middle_of_statement (frame.value,
> > + sal);
> 
> mid_statement should be a bool.

Fixed.

> > +
> > +      /* While for the ordinary backtrace printing of pc is based on
> > +	 MID_STATEMENT determined by stack.c:frame_show_address and
> the
> > +	 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, gdbarch, frame.value, sal, print_address,
> > +		    mid_statement, "");
> > +    }
> > +
> > +  annotate_shadowstack_frame_end ();
> > +  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
> > +  (gdbarch *gdbarch, const shadow_stack_print_options &print_options,
> > +   const shadow_stack_frame_info &frame, print_what print_what) {
> > +  do_with_buffered_output
> > +    (do_print_shadow_stack_frame_info, current_uiout, gdbarch,
> > +     print_options, frame, print_what); }
> > +
> > +
> > +/* Extract a char array which can be used for printing a reasonable
> > +   error message for REASON.  Note that in case REASON has the value
> > +   NO_ERROR the returned array is empty.  */
> > +
> > +static const char *
> > +ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
> > +{
> > +  switch (reason)
> > +    {
> > +    case ssp_unwind_stop_reason::no_error:
> > +      return _("");
> 
> The empty string doesn't need to be translated

True.

> All callers make sure that reason isn't no_error, so perhaps just remove this case?

I am not sure, if I can follow completely. You mean I should remove the case and replace
  gdb_assert_not_reached ("invalid reason"); with return "" ?

> > +    case ssp_unwind_stop_reason::memory_read_error:
> > +      return _("shadow stack memory read failure");
> > +    }
> > +
> > +  gdb_assert_not_reached ("invalid reason"); }
> > +
> > +
> > +/* Read the memory at shadow stack pointer SSP and assign it to
> > +   RETURN_VALUE.  In case we cannot read the memory, set REASON to
> > +   ssp_unwind_stop_reason::memory_read_error and return false.  */
> > +
> > +static bool
> > +read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
> > +			  CORE_ADDR *return_value,
> > +			  ssp_unwind_stop_reason *reason)
> 
> If a function assumes that a pointer it gets as argument is non-null as this one
> does with return_value and reason, then IMO it's better to get it as a
> reference instead of a pointer. Then the compiler makes sure that it is indeed
> non-null.

I agree, I changed that.

> > +{
> > +  /* 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);
> > +  if (!safe_read_memory_unsigned_integer (ssp, element_size, byte_order,
> > +					  return_value))
> > +    {
> > +      *reason = ssp_unwind_stop_reason::memory_read_error;
> > +      return false;
> > +    }
> > +
> > +  return true;
> > +}
> > +
> > +/*  If possible, return a shadow stack frame info which is COUNT elements
> > +    above the bottom of the shadow stack.  FRAME should point to the top
> > +    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.
> 
> Here is another place where I find "above/bottom/top" confusing, since
> stacks can grow up or grow down (as pointed out in the next comment in this
> function), and these terms mean different things in each case.

Yes, the comment is now 
"...above the outermost (oldest) element of the shadow stack."

I hope this is more straight-forward.

> > +    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
> > +  (gdbarch *gdbarch, const std::pair<CORE_ADDR, CORE_ADDR> range,
> > +   const ULONGEST count, shadow_stack_frame_info &frame) {
> > +  /* Compute the number of bytes on the shadow stack, starting at
> > +     FRAME->SSP, which depends on the direction the shadow stack
> > +     grows.  */
> > +  const int element_size
> > +    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
> > +  const unsigned long shadow_stack_bytes
> > +    = (gdbarch_stack_grows_down (gdbarch))
> 
> The parentheses around the function call are superfluous.

Fixed.

> > +       ? range.second - frame.ssp : frame.ssp - range.first +
> > + element_size;
> > +
> > +  gdb_assert ((shadow_stack_bytes % element_size) == 0);  const
> > + unsigned long shadow_stack_size
> > +    = shadow_stack_bytes / element_size;
> 
> This line fits 80 columns and doesn't need to be broken.
> 
> > +  const long level = shadow_stack_size - count;
> 
> In a comment further below you mention that level is 0-based, but doesn't
> this expression mean that it's 1-based? Perhaps it's missing a
> "- 1" term?
> Also, this doesn't work for GCS because it assumes all elements in the stack
> are return addresses. In GCS, the oldest element is a 0 entry
> The 0 entry is why this patch requires AArch64 to implement its own gdbarch
> top_addr_empty_shadow_stack hook.

There is clearly an issue with this function.  But I am not sure if I can follow here.
Shouldn't the issue be fixed when passing LEVEL instead of COUNT, as pointed out by you below?

And if not, do you have an idea how we can fix this? 

> Finally, not a concern for userspace support (and thus this patch
> series) but for OS and hypervisor software there's another kind of GCS entry
> which is the exception return record that is put on the stack when an
> exception is taken. That entry is 32 bytes in size.  I mention this just to
> illustrate that calculating the number of entries in the stack can be non-
> trivial.

Oh, I see. Isn't that already a problem with the current unwinding of the shadow
stack pointer for GCS? How would you fix it there ?

> Not sure how to account for these variations in generic code. Maybe add a
> new gdbarch method for returning the number of entries in the shadow
> stack?

To implement this, I believe we have two possible approaches:

(1) Assume we can determine the number of elements without unwinding each frame (as it is
currently the case). In this scenario, we could introduce a generic gdbarch hook to
retrieve the number of elements for architectures that require a different calculation.

(2) Unwind each shadow stack frame to obtain the trailing outermost frame, similar to how the
normal backtrace works.

Do you see any additional options?

Alternatively, we could keep the current approach for now (without the gdbarch hook, based on
the existing calculation) and only revise the implementation if other OS or hypervisor software
require different logic—once we have the ability to test those cases properly.
What are your thoughts?

> > +
> > +  /* COUNT exceeds the number of elements on the shadow stack.  Return
> the
> > +     starting shadow stack frame info FRAME.  */  if (level <= 0)
> > +    return std::optional<shadow_stack_frame_info> (frame);
> > +
> > +  CORE_ADDR new_ssp = update_shadow_stack_pointer
> > +    (gdbarch, frame.ssp, count, ssp_update_direction::bottom);
> 
> Shouldn't update_shadow_stack_pointer be called with 'level' rather than
> 'count' as argument?

Ah, you’re absolutely right. I’m certain this worked at some point before I posted it.
I’m not sure how this issue slipped in—and even more concerning that my tests didn’t catch it.

> > +  if (gdbarch_stack_grows_down (gdbarch))
> > +    gdb_assert (new_ssp < range.second);  else
> > +    gdb_assert (new_ssp >= range.first);
> > +
> > +  CORE_ADDR new_value;
> > +  if (!read_shadow_stack_memory (gdbarch, new_ssp, &new_value,
> > +				 &frame.unwind_stop_reason))
> > +    return {};
> > +
> > +  return std::optional<shadow_stack_frame_info>
> > +    ({new_ssp, new_value, (ULONGEST) level,
> > +      ssp_unwind_stop_reason::no_error});
> 
> This line causes a compilation error in arm-linux-gnueabihf, and I suppose
> other 32-bit targets as well:
> 
> /home/bauermann/src/binutils-gdb/gdb/shadow-stack.c:471:27: error:
> narrowing conversion of ‘(ULONGEST)((long int)level)’ from ‘ULONGEST’ {aka
> ‘long long unsigned int’} to ‘long unsigned int’ [-Werror=narrowing]
>   471 |     ({new_ssp, new_value, (ULONGEST) level,
> ssp_unwind_stop_reason::no_error});
>       |                           ^~~~~~~~~~~~~~~~
> 
> Also, it fits 80 columns and doesn't need to be broken.

Thanks, casting it as follows:
(unsigned long) level
should hopefully fix this. And then I will have more than 80 characters.

> > +}
> > +
> > +std::optional<shadow_stack_frame_info>
> > +shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info
> > +  (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range) {
> > +  /* 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 (gdbarch, this->ssp, 1,
> > +				   ssp_update_direction::bottom);
> > +
> > +  if (gdbarch_stack_grows_down (gdbarch))
> 
> To make this work for GCS, I had to add another if statement before the one
> above, to handle the case where new_ssp is at the initial 0 value:
> 
>   if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
>       && gdbarch_top_addr_empty_shadow_stack (gdbarch, new_ssp, range))
>     return {};
>   else if (gdbarch_stack_grows_down (gdbarch))
>     ⋮
> 
> Otherwise "bt shadow" will print the 0 entry:
> 
> (gdb) bt shadow
> #0  0x0000aaaaaaaa08ac in call2 at aarch64-gcs-return.c:65
> #1  0x0000aaaaaaaa08c0 in call1 at aarch64-gcs-return.c:71
> #2  0x0000aaaaaaaa09d4 in main at aarch64-gcs-return.c:110
> #3  0x0000000000000000 in ??

Yes, I missed that. Thanks.

> > +    {
> > +      /* The shadow stack grows downwards.  */
> > +      if (new_ssp >= range.second)
> > +	{
> > +	  /* We reached the bottom of the shadow stack.  */
> 
> In this case, we reached the top actually. This is why I find it confusing to use
> bottom/top as if it where agnostic to whether the stack grows up or down...

In my vision this is to "bottom" actually, if you look at this from a "stack" perspective.
But I changed this to "We reached the outermost element".

> > +	  return {};
> > +	}
> > +      /* We updated new_ssp towards the bottom 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 bottom of the shadow stack.  */
> > +	  return {};
> > +	}
> > +      /* We updated new_ssp towards the bottom of the shadow stack
> before,
> > +	 and new_ssp must be pointing to shadow stack memory.  */
> > +      gdb_assert (new_ssp <= range.second);
> > +    }
> > +
> > +  CORE_ADDR new_value;
> > +  if (!read_shadow_stack_memory (gdbarch, new_ssp, &new_value,
> > +				 &this->unwind_stop_reason))
> > +    return {};
> > +
> > +  return std::optional<shadow_stack_frame_info>
> > +    ({new_ssp, new_value, this->level + 1,
> > +      ssp_unwind_stop_reason::no_error});
> 
> This line fits 80 columns and doesn't need to be broken.

Even though there is this soft limit, I think it looks nicer unbroken, so I changed this.

> > +}
> > +
> > +/* Print all elements on the shadow stack or just the innermost
> COUNT_EXP
> > +   frames.  */
> > +
> > +static void
> > +backtrace_shadow_command (const shadow_stack_print_options
> &print_options,
> > +			  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))
> > +    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 target."));
> 
> At least for AArch64, GCS can be enabled or disabled per-thread so I would
> say "not enabled for the current thread" here.

I agree, thanks!

> > +  /* 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.  */
> 
> Hm, I'm not sure if aarch64-linux supports GCS switching. The kernel's gcs.rst
> documentation says:
> 
>   * The architecture provides instructions for switching between guarded
>     control stacks with checks to ensure that the new stack is a valid
>     target for switching.
> 
> And a comment in AArch64's implementation of the map_shadow_stack
> syscall says:
> 
>   /*
>    * Put a cap token at the end of the allocated region so it
>    * can be switched to.
>    */
> 
> But I don't see anything positively mention that it's supported.
> I assume so, but I'll have to confirm. If that is the case, later I will submit a
> patch with any necessary changes.

Ok, so keeping the current comment is fine for you?

> > +  std::pair<CORE_ADDR, CORE_ADDR> range;
> > +  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> *start_ssp,
> > +						     &range))
> 
> Shouldn't this if condition also check
> gdbarch_top_addr_empty_shadow_stack?

For x86 this logic is fine, but I assume for GCS this does not work for empty
shadow stacks?

> > +    {
> > +      /* If the current shadow stack pointer does not point to shadow
> > +	 stack memory, the shadow stack is empty.  */
> > +      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;
> > +  CORE_ADDR new_value;
> > +  if (read_shadow_stack_memory (gdbarch, *start_ssp, &new_value,
> &reason))
> > +    current = {*start_ssp, new_value, 0,
> > +	       ssp_unwind_stop_reason::no_error};
> 
> This line fits in 80 columns and doesn't need to be broken.

Yes, I changed that.

> > +
> > +  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.  */
> 
> Actually, trailing will be the frame above the one from which we should start
> printing. For example, if count is -3
> get_trailing_outermost_shadow_stack_frame_info will return the frame
> "which is COUNT elements above the bottom of the shadow stack", which in
> this case is the 4th frame counting from the oldest frame. But we need to
> start printing from the 3rd oldest frame ...

> > +      if (count < 0)
> > +	{
> > +	  trailing = get_trailing_outermost_shadow_stack_frame_info
> > +		       (gdbarch, range, std::abs (count), *current);
> 
> ... so this call should pass std::abs (count) - 1 as argument.

Hm, I am not sure if I can follow. With the fix for passing level instead
of count my current output looks as follow:
       
~~~
Breakpoint 1, call2 () at /tmp/amd64-shadow-stack.c:21
21        return 42; /* break call2.  */
(gdb) bt -past-main
#0  call2 () at /tmp /amd64-shadow-stack.c:21
#1  0x0000555555555142 in call1 () at /tmp/amd64-shadow-stack.c:27
#2  0x0000555555555153 in main () at /tmp/amd64-shadow-stack.c:38
#3  0x00007ffff7c2a1ca in __libc_start_call_main ([...]
#4  0x00007ffff7c2a28b in __libc_start_main_impl ([...]) at ../csu/libc-start.c:360
#5  0x0000555555555065 in _start ()
(gdb) bt -past-main -3
#3  0x00007ffff7c2a1ca in __libc_start_call_main ([...]) at ../sysdeps/nptl/libc_start_call_main.h:58
#4  0x00007ffff7c2a28b in __libc_start_main_impl ([...])    at ../csu/libc-start.c:360
#5  0x0000555555555065 in _start ()
(gdb) bt shadow -3
#2  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:74
#3  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:128
#4  0x0000555555555065 in _start
(gdb) bt shadow
#0  0x0000555555555142 in call1 at /tmp/amd64-shadow-stack.c:28
#1  0x0000555555555153 in main at /tmp/amd64-shadow-stack.c:39
#2  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:74
#3  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:128
#4  0x0000555555555065 in _start
~~~

Which is correct from my perspective, but maybe I am missing something. 
I think I lost the overview a bit, there are a number of issues in my code here, unfortunately. :/
Note that for this output I still print the level starting at 0. Will apply this change once the other issues are clear.

> > +	  if (!trailing.has_value ())
> > +	    reason = current->unwind_stop_reason;
> > +	}
> > +    }
> > +
> > +  if (!trailing.has_value ())
> > +    {
> > +      if (reason > ssp_unwind_stop_reason::no_error)
> > +	error (_("Cannot print shadow stack backtrace: %s.\n"),
> > +	       ssp_unwind_stop_reason_to_err_string (reason));
> > +      else
> > +	gdb_assert_not_reached ("invalid reason");
> > +    }
> 
> It's clearer to write this as:
> 
>   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));
>     }

I agree, I changed that.

> > +
> > +  current = trailing;
> > +  while (current.has_value () && count--)
> 
> The GDB Coding Standard says that comparing a number to zero should be
> explicit, so "count-- != 0" here. I see that backtrace_command_1 already has
> the implicit comparison, but it's probably a mistake there (or just old code).

Yes, fixed. Thanks!

> 
> Also, another way of expressing the above would be:
> 
>   for (current = trailing; current.has_value () && count != 0; count--)
> 
> I personally think it's clearer (especially because the comparison and
> decrement are separate), but it's more of a personal preference so I don't
> really mind either way.

I agree, and took your suggestion, thanks !:) 

> 
> > +    {
> > +      QUIT;
> > +
> > +      print_shadow_stack_frame_info (gdbarch, print_options, *current,
> > +				     LOCATION);
> > +
> > +      trailing = current;
> > +      current = current->unwind_prev_shadow_stack_frame_info (gdbarch,
> > +							      range);
> 
> This line fits in 80 columns and doesn't need to be broken.

Hm, weird, I again count more than 80 columns.

> > +    }
> > +
> > +  /* If we've stopped before the end, mention that.  */  if (current
> > + && from_tty)
> 
> While it's correct to use an std::optional in this way to check whether it has a
> value, IMHO it's clearer to be more explicit and use the has_value method.
> It's also consistent with the rest of the code in this function.

I agree. I fixed that.

> > +    gdb_printf (_("(More shadow stack frames follow...)\n"));
> > +
> > +  /* 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.has_value ()
> 
> If I'm not mistaken, trailing.has_value () is always true at this point.

I agree, this logic is from backtrace_command_1. 
I think adding an assert here should be fine instead, so I suggest the following:

~~~
[...]
  /* 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.  */
[...]
~~~

Please let me know if you think otherwise.

> > +      && (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 (gdbarch, trailing->ssp),
> > +		ssp_unwind_stop_reason_to_err_string
> > +		  (trailing->unwind_stop_reason));
> > +}
> 
> <snip>
> 
> > diff --git a/gdb/stack.c b/gdb/stack.c index 40756f74a00..30a7d8621be
> > 100644
> > --- a/gdb/stack.c
> > +++ b/gdb/stack.c
> > @@ -50,6 +50,7 @@
> >  #include "linespec.h"
> >  #include "cli/cli-utils.h"
> >  #include "objfiles.h"
> > +#include "shadow-stack.h"
> >
> >  #include "symfile.h"
> >  #include "extension.h"
> > @@ -86,7 +87,7 @@ const char print_frame_info_source_and_location[] =
> > "source-and-location";  const char
> > print_frame_info_location_and_address[] = "location-and-address";
> > const char print_frame_info_short_location[] = "short-location";
> >
> > -static const char *const print_frame_info_choices[] =
> > +const char *const print_frame_info_choices[] =
> >  {
> >    print_frame_info_auto,
> >    print_frame_info_source_line,
> > @@ -962,11 +963,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++) @@
> > -1016,7 +1015,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.  */
> 
> Change comment here to /* See stack.h.  */

Fixed.

> 
> > -static bool
> > +bool
> >  should_print_location (print_what print_what)  {
> >    return (print_what == LOCATION
> > @@ -1025,14 +1024,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) @@ -1298,7 +1292,7 @@
> > get_last_displayed_sal ()
> >
> >  /* Find the function name for the symbol SYM.  */
> 
> Change comment here to /* See stack.h.  */

Right, fixed.

> >
> > -static gdb::unique_xmalloc_ptr<char>
> > +gdb::unique_xmalloc_ptr<char>
> >  find_symbol_funname (const symbol *sym)  {
> >    gdb::unique_xmalloc_ptr<char> funname;
> 
> <snip>
> 
> > +/* Completer for the "backtrace shadow" sub-command.  */
> > +
> > +static void
> > +backtrace_shadow_command_completer (struct cmd_list_element
> *ignore,
> > +				    completion_tracker &tracker,
> > +				    const char *text, const char */*word*/) {
> > +  const auto group
> > +    = make_backtrace_shadow_options_def_group (nullptr);
> > +  if (gdb::option::complete_options
> > +      (tracker, &text,
> gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group))
> > +    return;
> > +
> > +  const char *word = advance_to_expression_complete_word_point
> > +(tracker, text);
> > +  expression_completer (ignore, tracker, text, word); }
> 
> I would put this function in shadow-stack.c.

Yes, I could do that. But I believe once we changed "bt shadow" to "bt -shadow",
We don't need this anymore. But if we do, I'll move it. 

> 
> <snip>
> 
> > diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> > b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> > index 0ae172d7c41..4563e49d9e4 100644
> > --- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> > +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> > @@ -109,6 +109,94 @@ 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"
> > + ""]
> 
> I suggest adding a descriptive test name to the get_valueof calls above.
> Perhaps "read newest shadow stack entry at main/call1/call2"?

Ok, added.

> 
> > +    set frame0 "#0\[ \t\]*$sspval_call2 in call1$fill"
> > +    set frame1 "#1\[ \t\]*$sspval_call1 in main$fill"
> > +    set frame2 "#2\[ \t\]*$sspval_main$fill"
> > +
> > +    # We can 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" \
> > +	[multi_line \
> > +	    "$frame0" \
> > +	    "$frame1" \
> > +	    "$frame2" \
> > +	    ".*" ] \
> > +	"test shadow stack backtrace until the bottom of the stack."
> > +
> > +    gdb_test "bt shadow 2" \
> > +	[multi_line \
> > +	    "$frame0" \
> > +	    "$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"
> 
> In my GCS testcase this test is passing, even though the command is printing
> more than one frame:
> 
> bt shadow -1
> #3  0x0000aaaaaaaa08c0 in call1 at /home/bauermann/src/binutils-
> gdb/gdb/testsuite/gdb.arch/aarch64-gcs-return.c:71
> #4  0x0000aaaaaaaa09d4 in main at /home/bauermann/src/binutils-
> gdb/gdb/testsuite/gdb.arch/aarch64-gcs-return.c:110
> #5  0x0000000000000000 in ??
> (gdb) PASS: gdb.arch/aarch64-gcs-backtrace.exp: test shadow stack backtrace
> with a negative value for count
>
> So there's one bug in GDB, and one in the testcase. To fix the testcase bug, I
> believe you just have to add '^' to the beginning of the pattern.
> The bug in GDB was fixed by changing the expression to calculate 'level'
> in get_trailing_outermost_shadow_stack_frame_info as I mentioned above.
> 
> --
> Thiago

Thanks a lot for spotting this. I fixed the GDB bug by passing "level" instead of count
and added the '^' in the testcase, just like you suggested.

Christina

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-11-17 20:14     ` Schimpe, Christina
@ 2025-11-26  4:07       ` Thiago Jung Bauermann
  2025-11-26 16:29         ` Thiago Jung Bauermann
  2026-01-15 14:05         ` Schimpe, Christina
  0 siblings, 2 replies; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-11-26  4:07 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

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

> Hi Thiago,
>
> Thanks for taking the time to provide this review—I really appreciate your detailed input!

You're most welcome. Thank you for pushing the shadow stack feature
forward!

> I’ve implemented part of your feedback, and there are still a few open points I’d like to clarify.
> I hope I didn’t miss any of your comments, as the review has become quite comprehensive.

Indeed, the review ended up more detailed than I anticipated because I'm
using the review process to also develop the AArch64 side. This made me
dig into the code quite a bit.

> Please find my responses and open questions below. Looking forward to your thoughts!

Thanks!

>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Freitag, 31. Oktober 2025 05:02
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: gdb-patches@sourceware.org
>> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
>> stack backtrace.
>> 
>> Christina Schimpe <christina.schimpe@intel.com> writes:
>> 
>> > Example for the output of 'bt shadow' on amd64 linux:
>> > ~~
>> > (gdb) bt shadow
>> > /#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
>> > /#1  0x000000000040112f in main at amd64-shadow-stack.c:21
>> > /#2  0x00007ffff7c3fe70 in __libc_start_call_main at
>> > ../sysdeps/nptl/libc_start_call_main.h:58
>> > /#3  0x00007ffff7c3ff20 in __libc_start_main_impl at
>> > ../csu/libc-start.c:128
>> > /#4  0x0000000000401045 in _start
>> > ~~
>> 
>> Here's an example output in my aarch64-linux test VM:
>> 
>> (gdb) bt shadow
>> #0  0x0000aaaaaaaa08ac in call2 at aarch64-gcs-return.c:65
>> #1  0x0000aaaaaaaa08c0 in call1 at aarch64-gcs-return.c:71
>> #2  0x0000aaaaaaaa09d4 in main at aarch64-gcs-return.c:110
>> #3  0x0000000000000000 in ??
>> 
>> And here is the regular backtrace at the same point:
>> 
>> (gdb) bt
>> #0  call3 () at aarch64-gcs-return.c:58
>> #1  0x0000aaaaaaaa08ac in call2 () at aarch64-gcs-return.c:64
>> #2  0x0000aaaaaaaa08c0 in call1 () at aarch64-gcs-return.c:70
>> #3  0x0000aaaaaaaa09d4 in main () at aarch64-gcs-return.c:107
>> 
>> I have a few comments on this output, at least as it is appears for GCS. I'm
>> assuming x86 shadow stack output is similar:
>> 
>> 1. The first thing that I notice is that the number of the frames don't match
>> between the regular stack and the shadow stack: frame 0 in the regular stack
>> doesn't exist in the shadow stack, regular frame 1 is shadow frame 0, and so
>> on.
>> 
>> I think this is confusing. It makes sense that frame 0 doesn't appear in the
>> shadow stack backtrace because there really isn't an entry for it in the
>> shadow stack, but I think the shadow entries should start with number 1, to
>> be consistent.
>
> Mh, it's probably better to align, I agree.
> I'll go with starting frame #1 in the v2, and add a comment about that in the
> commit message.
>
>> 2. The line numbers corresponding to the return addresses don't match
>> between the regular backtrace and the shadow backtrace, even though the
>> return addresses are the same. I would say this is a bug. Perhaps there can be
>> a test making sure they match, if it's not too complicated?
>
> The reason for that is explained in a comment in do_print_shadow_stack_frame_info:
>
>   /* In contrast to find_frame_sal which is used for the ordinary backtrace
>      command, we always want to print the line that is actually referred
>      to by the address in the linetable.  That's why we always pass 0 here
>      as second argument.  */

I don't understand why we want to always print the line that is actually
referred to by the address in the linetable. find_frame_sal has this
comment:

  /* If FRAME is not the innermost frame, that normally means that
     FRAME->pc points at the return instruction (which is *after* the
     call instruction), and we want to get the line containing the
     call (because the call is where the user thinks the program is).

Doesn't that also apply to the addresses in the shadow stack?

> Do you think we should align this with the behavior of the ordinary backtrace command
> here as well?

I think so, but maybe there's something I'm not considering.

> I’m not entirely sure if that approach might be too straightforward, since we can’t simply
> call the get_frame_address_in_block function as the ordinary bt command does when
> invoking it from find_frame_sal.

From what I understand of the big comment in get_frame_address_in_block,
we can create an equivalent function for shadow stack frames: it should
return an adjusted PC for frames corresponding to normal function calls,
which are all except:

1. sentinel frame
2. signal frame
3. dummy frame 

These are easy to recognise (or in the case of 3, can be made easy to
recognise) in the shadow stack:

1. Since the shadow stack doesn't have frame #0, this_frame is never the
sentinel one. And next_frame will be the "sentinel" if this_frame is the
first one in the shadow stack.

2. The Linux kernel on both AArch64 and Intel puts a marker in the
shadow stack when a signal is handled, so we can look for it in the
shadow stack to recognize the signal frame.

3. We can change GDB to also put a marker in the shadow stack when it
does an inferior function call, and look for it in the shadow stack.

>> > +/* 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_frame_sal).  Otherwise, we would have some PC range if the
>> 
>> In the case of stack.c:frame_show_address, SAL comes from find_frame_sal,
>> but in this case it comes from find_pc_line, right?
>
> Yes, it comes from find_sal_for_pc (when rebased on latest master, find_pc_line was renamed).
>
>> Does the comment still apply in this case?
>
> Good question, I remember I stumbled over this a couple of times when implementing this.
> In any case, the comment needs to be corrected to "(SAL comes from find_sal_for_pc)."
> If we need this check, I am not sure. In my experiments with inlined functions I could
> not reproduce that scenario. But I kept it, to be safe.

From my reading of find_pc_line / find_sal_for_pc, it doesn't return a
sal with line number but no PC. But I'm not very familiar with this part
of the code. But I don't mind keeping this check (with an adjusted
comment, as you mention).

>> > +     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;
>> > +}

<snip>

>> > +/* Extract a char array which can be used for printing a reasonable
>> > +   error message for REASON.  Note that in case REASON has the value
>> > +   NO_ERROR the returned array is empty.  */
>> > +
>> > +static const char *
>> > +ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
>> > +{
>> > +  switch (reason)
>> > +    {
>> > +    case ssp_unwind_stop_reason::no_error:
>> > +      return _("");
>> 
>> The empty string doesn't need to be translated
>
> True.
>
>> All callers make sure that reason isn't no_error, so perhaps just remove this case?
>
> I am not sure, if I can follow completely. You mean I should remove the case and replace
>   gdb_assert_not_reached ("invalid reason"); with return "" ?

I mean just remove this case from the switch. Then this function will
assert whenever reason != ssp_unwind_stop_reason::memory_read_error.

>> > +    case ssp_unwind_stop_reason::memory_read_error:
>> > +      return _("shadow stack memory read failure");
>> > +    }
>> > +
>> > +  gdb_assert_not_reached ("invalid reason");
>> > +}

<snip>

>> > +/*  If possible, return a shadow stack frame info which is COUNT elements
>> > +    above the bottom of the shadow stack.  FRAME should point to the top
>> > +    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.
>> 
>> Here is another place where I find "above/bottom/top" confusing, since
>> stacks can grow up or grow down (as pointed out in the next comment in this
>> function), and these terms mean different things in each case.
>
> Yes, the comment is now 
> "...above the outermost (oldest) element of the shadow stack."
>
> I hope this is more straight-forward.

There's still "above" and "top". A suggestion:

"If possible, return a shadow stack frame info which is COUNT elements
 newer than the outermost (oldest) element of the shadow stack.  FRAME
 should point to the newest element of the shadow stack..."

>> > +    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
>> > +  (gdbarch *gdbarch, const std::pair<CORE_ADDR, CORE_ADDR> range,
>> > +   const ULONGEST count, shadow_stack_frame_info &frame) {
>> > +  /* Compute the number of bytes on the shadow stack, starting at
>> > +     FRAME->SSP, which depends on the direction the shadow stack
>> > +     grows.  */
>> > +  const int element_size
>> > +    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
>> > +  const unsigned long shadow_stack_bytes
>> > +    = (gdbarch_stack_grows_down (gdbarch))
>> 
>> The parentheses around the function call are superfluous.
>
> Fixed.
>
>> > +       ? range.second - frame.ssp : frame.ssp - range.first +
>> > + element_size;
>> > +
>> > +  gdb_assert ((shadow_stack_bytes % element_size) == 0);  const
>> > + unsigned long shadow_stack_size
>> > +    = shadow_stack_bytes / element_size;
>> 
>> This line fits 80 columns and doesn't need to be broken.
>> 
>> > +  const long level = shadow_stack_size - count;
>> 
>> In a comment further below you mention that level is 0-based, but doesn't
>> this expression mean that it's 1-based? Perhaps it's missing a
>> "- 1" term?
>> Also, this doesn't work for GCS because it assumes all elements in the stack
>> are return addresses. In GCS, the oldest element is a 0 entry
>> The 0 entry is why this patch requires AArch64 to implement its own gdbarch
>> top_addr_empty_shadow_stack hook.
>
> There is clearly an issue with this function.  But I am not sure if I can follow here.
> Shouldn't the issue be fixed when passing LEVEL instead of COUNT, as pointed out by you below?

Passing level instead of count is necessary but not sufficient.

The function's documentation comment says that it returns "a shadow
stack frame info which is COUNT elements newer than the outermost
(oldest) element of the shadow stack". One example:

Suppose that the shadow stack has 5 elements and count is 2. Then level
as calculated by your patch is 3 (5 - 2). So frame.ssp will be adjusted by
3 elements by the call to update_shadow_stack_pointer further
below. This is wrong though, because it makes new_ssp point to 1 element
newer than the oldest element of the shadow stack.

If level is calculated instead as level = shadow_stack_size - count - 1
then in this example it will be 2 and new_ssp will point to 2 elements
newer than the oldest element of the shadow stack, which matches the
documentation comment.

But that still doesn't work for AArch64, because every shadow stack
there has an extra element at the beginning which also needs to be
discounted, so in this case it should be:

  const long level = shadow_stack_size - count - 2;

But then it's wrong for Intel.

> And if not, do you have an idea how we can fix this? 

I suggest adding a gdbarch method which given RANGE, returns how many
entries there are in the shadow stack. And to calculate level as:

  const long level = shadow_stack_size - count - 1;

Where count is the result of the gdbarch hook.

>> Finally, not a concern for userspace support (and thus this patch
>> series) but for OS and hypervisor software there's another kind of GCS entry
>> which is the exception return record that is put on the stack when an
>> exception is taken. That entry is 32 bytes in size.  I mention this just to
>> illustrate that calculating the number of entries in the stack can be non-
>> trivial.
>
> Oh, I see. Isn't that already a problem with the current unwinding of the shadow
> stack pointer for GCS? How would you fix it there ?

It's not a problem for now because GCS support is only implemented for
Linux userspace inferiors. It's a bridge to be crossed in the future. :)

I just mentioned this case to illustrate that assuming that all entries
in the shadow stack have the same size is fragile. But for now it's good
enough, so perhaps I shouldn't have mentioned it.

>> Not sure how to account for these variations in generic code. Maybe add a
>> new gdbarch method for returning the number of entries in the shadow
>> stack?
>
> To implement this, I believe we have two possible approaches:
>
> (1) Assume we can determine the number of elements without unwinding each frame (as it is
> currently the case). In this scenario, we could introduce a generic gdbarch hook to
> retrieve the number of elements for architectures that require a different calculation.
>
> (2) Unwind each shadow stack frame to obtain the trailing outermost frame, similar to how the
> normal backtrace works.
>
> Do you see any additional options?

No, I can also only think of these.

> Alternatively, we could keep the current approach for now (without the gdbarch hook, based on
> the existing calculation) and only revise the implementation if other OS or hypervisor software
> require different logic—once we have the ability to test those cases properly.
> What are your thoughts?

The current approach isn't feasible because level needs to be calculated
in one way for Intel, and a different way for AArch64. I suggest the
gdbarch hook option.

>> > +
>> > +  /* COUNT exceeds the number of elements on the shadow stack.  Return the
>> > +     starting shadow stack frame info FRAME.  */
>> > +  if (level <= 0)
>> > +    return std::optional<shadow_stack_frame_info> (frame);
>> > +
>> > +  CORE_ADDR new_ssp = update_shadow_stack_pointer
>> > +    (gdbarch, frame.ssp, count, ssp_update_direction::bottom);
>> 
>> Shouldn't update_shadow_stack_pointer be called with 'level' rather than
>> 'count' as argument?
>
> Ah, you’re absolutely right. I’m certain this worked at some point before I posted it.
> I’m not sure how this issue slipped in—and even more concerning that my tests didn’t catch it.
>
>> > +  if (gdbarch_stack_grows_down (gdbarch))
>> > +    gdb_assert (new_ssp < range.second);  else
>> > +    gdb_assert (new_ssp >= range.first);
>> > +
>> > +  CORE_ADDR new_value;
>> > +  if (!read_shadow_stack_memory (gdbarch, new_ssp, &new_value,
>> > +				 &frame.unwind_stop_reason))
>> > +    return {};
>> > +
>> > +  return std::optional<shadow_stack_frame_info>
>> > +    ({new_ssp, new_value, (ULONGEST) level,
>> > +      ssp_unwind_stop_reason::no_error});
>> 
>> This line causes a compilation error in arm-linux-gnueabihf, and I suppose
>> other 32-bit targets as well:
>> 
>> /home/bauermann/src/binutils-gdb/gdb/shadow-stack.c:471:27: error:
>> narrowing conversion of ‘(ULONGEST)((long int)level)’ from ‘ULONGEST’ {aka
>> ‘long long unsigned int’} to ‘long unsigned int’ [-Werror=narrowing]
>>   471 |     ({new_ssp, new_value, (ULONGEST) level,
>> ssp_unwind_stop_reason::no_error});
>>       |                           ^~~~~~~~~~~~~~~~
>> 
>> Also, it fits 80 columns and doesn't need to be broken.
>
> Thanks, casting it as follows:
> (unsigned long) level
> should hopefully fix this. And then I will have more than 80 characters.

Ok.

>> > +}
>> > +
>> > +std::optional<shadow_stack_frame_info>
>> > +shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info
>> > +  (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range) {
>> > +  /* 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 (gdbarch, this->ssp, 1,
>> > +				   ssp_update_direction::bottom);
>> > +
>> > +  if (gdbarch_stack_grows_down (gdbarch))
>> 
>> To make this work for GCS, I had to add another if statement before the one
>> above, to handle the case where new_ssp is at the initial 0 value:
>> 
>>   if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
>>       && gdbarch_top_addr_empty_shadow_stack (gdbarch, new_ssp, range))
>>     return {};
>>   else if (gdbarch_stack_grows_down (gdbarch))
>>     ⋮
>> 
>> Otherwise "bt shadow" will print the 0 entry:
>> 
>> (gdb) bt shadow
>> #0  0x0000aaaaaaaa08ac in call2 at aarch64-gcs-return.c:65
>> #1  0x0000aaaaaaaa08c0 in call1 at aarch64-gcs-return.c:71
>> #2  0x0000aaaaaaaa09d4 in main at aarch64-gcs-return.c:110
>> #3  0x0000000000000000 in ??
>
> Yes, I missed that. Thanks.
>
>> > +    {
>> > +      /* The shadow stack grows downwards.  */
>> > +      if (new_ssp >= range.second)
>> > +	{
>> > +	  /* We reached the bottom of the shadow stack.  */
>> 
>> In this case, we reached the top actually. This is why I find it confusing to use
>> bottom/top as if it where agnostic to whether the stack grows up or down...
>
> In my vision this is to "bottom" actually, if you look at this from a "stack" perspective.
> But I changed this to "We reached the outermost element".

Thanks.

>> > +  /* 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.  */
>> 
>> Hm, I'm not sure if aarch64-linux supports GCS switching. The kernel's gcs.rst
>> documentation says:
>> 
>>   * The architecture provides instructions for switching between guarded
>>     control stacks with checks to ensure that the new stack is a valid
>>     target for switching.
>> 
>> And a comment in AArch64's implementation of the map_shadow_stack
>> syscall says:
>> 
>>   /*
>>    * Put a cap token at the end of the allocated region so it
>>    * can be switched to.
>>    */
>> 
>> But I don't see anything positively mention that it's supported.
>> I assume so, but I'll have to confirm. If that is the case, later I will submit a
>> patch with any necessary changes.
>
> Ok, so keeping the current comment is fine for you?

Yes. Sorry, that was another confusing digression from me.

>> > +  std::pair<CORE_ADDR, CORE_ADDR> range;
>> > +  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
>> > +						     &range))
>> 
>> Shouldn't this if condition also check
>> gdbarch_top_addr_empty_shadow_stack?
>
> For x86 this logic is fine, but I assume for GCS this does not work for empty
> shadow stacks?

Indeed, we need the extra check for GCS.

>> > +    {
>> > +      /* If the current shadow stack pointer does not point to shadow
>> > +	 stack memory, the shadow stack is empty.  */
>> > +      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;
>> > +  CORE_ADDR new_value;
>> > +  if (read_shadow_stack_memory (gdbarch, *start_ssp, &new_value, &reason))
>> > +    current = {*start_ssp, new_value, 0,
>> > +	       ssp_unwind_stop_reason::no_error};
>> 
>> This line fits in 80 columns and doesn't need to be broken.
>
> Yes, I changed that.
>
>> > +
>> > +  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.  */
>> 
>> Actually, trailing will be the frame above the one from which we should start
>> printing. For example, if count is -3
>> get_trailing_outermost_shadow_stack_frame_info will return the frame
>> "which is COUNT elements above the bottom of the shadow stack", which in
>> this case is the 4th frame counting from the oldest frame. But we need to
>> start printing from the 3rd oldest frame ...
>
>> > +      if (count < 0)
>> > +	{
>> > +	  trailing = get_trailing_outermost_shadow_stack_frame_info
>> > +		       (gdbarch, range, std::abs (count), *current);
>> 
>> ... so this call should pass std::abs (count) - 1 as argument.
>
> Hm, I am not sure if I can follow. With the fix for passing level instead
> of count my current output looks as follow:
>        
> ~~~
> Breakpoint 1, call2 () at /tmp/amd64-shadow-stack.c:21
> 21        return 42; /* break call2.  */
> (gdb) bt -past-main
> #0  call2 () at /tmp /amd64-shadow-stack.c:21
> #1  0x0000555555555142 in call1 () at /tmp/amd64-shadow-stack.c:27
> #2  0x0000555555555153 in main () at /tmp/amd64-shadow-stack.c:38
> #3  0x00007ffff7c2a1ca in __libc_start_call_main ([...]
> #4  0x00007ffff7c2a28b in __libc_start_main_impl ([...]) at ../csu/libc-start.c:360
> #5  0x0000555555555065 in _start ()
> (gdb) bt -past-main -3
> #3  0x00007ffff7c2a1ca in __libc_start_call_main ([...]) at ../sysdeps/nptl/libc_start_call_main.h:58
> #4  0x00007ffff7c2a28b in __libc_start_main_impl ([...])    at ../csu/libc-start.c:360
> #5  0x0000555555555065 in _start ()
> (gdb) bt shadow -3
> #2  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:74
> #3  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:128
> #4  0x0000555555555065 in _start
> (gdb) bt shadow
> #0  0x0000555555555142 in call1 at /tmp/amd64-shadow-stack.c:28
> #1  0x0000555555555153 in main at /tmp/amd64-shadow-stack.c:39
> #2  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:74
> #3  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:128
> #4  0x0000555555555065 in _start
> ~~~
>
> Which is correct from my perspective, but maybe I am missing something. 
> I think I lost the overview a bit, there are a number of issues in my code here, unfortunately. :/
> Note that for this output I still print the level starting at 0. Will apply this change once the other issues are clear.

I think this is working for you because of the off-by-one error in the
calculation of level in get_trailing_outermost_shadow_stack_frame_info
that I mentioned above. I think the off-by-one error here compensates
the other one.

>> > +    {
>> > +      QUIT;
>> > +
>> > +      print_shadow_stack_frame_info (gdbarch, print_options, *current,
>> > +				     LOCATION);
>> > +
>> > +      trailing = current;
>> > +      current = current->unwind_prev_shadow_stack_frame_info (gdbarch,
>> > +							      range);
>> 
>> This line fits in 80 columns and doesn't need to be broken.
>
> Hm, weird, I again count more than 80 columns.

Strange indeed.

>> > +    }
>> > +
>> > +  /* If we've stopped before the end, mention that.  */  if (current
>> > + && from_tty)
>> 
>> While it's correct to use an std::optional in this way to check whether it has a
>> value, IMHO it's clearer to be more explicit and use the has_value method.
>> It's also consistent with the rest of the code in this function.
>
> I agree. I fixed that.
>
>> > +    gdb_printf (_("(More shadow stack frames follow...)\n"));
>> > +
>> > +  /* 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.has_value ()
>> 
>> If I'm not mistaken, trailing.has_value () is always true at this point.
>
> I agree, this logic is from backtrace_command_1. 
> I think adding an assert here should be fine instead, so I suggest the following:
>
> ~~~
> [...]
>   /* 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.  */
> [...]
> ~~~
>
> Please let me know if you think otherwise.

Looks good to me.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer.
  2025-11-17 11:18     ` Schimpe, Christina
@ 2025-11-26  4:19       ` Thiago Jung Bauermann
  2025-12-30 10:39         ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-11-26  4:19 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

Hello Christina,

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

> Thanks a lot for this detailed review!
> I applied most of your comments, please find my feedback to your review below.

You're welcome!

>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Friday, 31 October 2025 02:32
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: gdb-patches@sourceware.org
>> Subject: Re: [PATCH 1/9] gdb: Generalize handling of the shadow stack
>> pointer.
>> 
>> Christina Schimpe <christina.schimpe@intel.com> writes:
>> 
>> > -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)
>> 
>> IIUC, the '<=' comparison above isn't preserved by this patch. This function is
>> replaced by dwarf2_prev_ssp, which uses
>> gdbarch_address_in_shadow_stack_memory_range for this if condition,
>> whose comparison in find_addr_mem_range is:
>> 
>>       bool addr_in_mem_range
>>         = (addr >= map.start_address && addr < map.end_address);
>> 
>> Is this intended?
>
> Arg, thanks for catching that!
>
> I think I missed that because I introduced a typo/bug in the call
>
> 	      || gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> 							       ssp,
> 							       &range))
>
> which made the unwinding work properly in case of amd64.
> However, the proper fix should be to pass new_ssp to gdbarch_address_in_shadow_stack_memory_range
> instead, and to implement gdbarch_top_addr_empty_shadow_stack also for amd64.
>
> Does that make sense?

Yes, I agree.

>> > -	    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;
>> > -}
>> 
>> <snip>
>> 
>> > diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c new file mode
>> > 100644 index 00000000000..d153d5fc846
>> > --- /dev/null
>> > +++ b/gdb/shadow-stack.c
>> > @@ -0,0 +1,167 @@
>> > +/* Manage a shadow stack pointer for GDB, the GNU debugger.
>> > +
>> > +   Copyright (C) 2024-2025 Free Software Foundation, Inc.
>> 
>> Should this really start at 2024? According to Andrew Burgess¹:
>
> Yes, 2024 is correct in this case since our gdb-oneapi supported bt shadow since 2024. 

Ah, right. Thanks for clarifying.

>> > +enum class ssp_update_direction
>> > +{
>> > +  /* Update ssp towards the bottom of the shadow stack.  */
>> > +  bottom = 0,
>> > +
>> > +  /* Update ssp towards the top of the shadow stack.  */
>> > +  top
>> > +};
>> 
>> I find the bottom/top nomenclature confusing, especially because it's
>> supposed to mean the same thing whether the stack grows up or down. In
>> my mind, if the stack grow down then top means "oldest element", but if the
>> stack grows up, then top means "newest element".
>> But in this patch it seems that top means "newest element" regardless of the
>> direction of stack growth.
>
> Yes, that was my understanding. So independent in which direction a shadow stack
> grows based on the architecture/OS, top always means newest element.  But I think
> it is not a problem to take one of your suggestions.

Thank you. In my view it also matches the nomenclature in frame.h, which
also doesn't use vertical concepts. E.g.,

  /* Given a FRAME, return the next (more inner, younger) or previous
     (more outer, older) frame.  */
  extern frame_info_ptr get_prev_frame (const frame_info_ptr &);
  extern frame_info_ptr get_next_frame (const frame_info_ptr &);

>> I would suggest changing the enum names above to something that's not
>> related to the vertical axis, so that their meaning will be clear regardless of
>> which direction the stack grows. A few suggestions:
>> shrink/grow, older/younger, outer/inner.
>
> I'd take outer/inner and describe it as follows:
>
> 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
> };
>
> Is that understandable ?

Yes, thanks for making the change.

>> > +/* See shadow-stack.h.  */
>> > +
>> > +void shadow_stack_push (gdbarch *gdbarch, regcache *regcache,
>> 
>> There's no need for a gdbarch argument. You can get it from the regcache.
>
> Fixed.
>
>> > +			const CORE_ADDR new_addr)
>> > +{
>> > +  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::top);
>> > +
>> > +  /* If NEW_SSP does not point to shadow stack memory, we assume the stack
>> > +     is full.  */
>> > +  std::pair<CORE_ADDR, CORE_ADDR> range;
>> > +  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch,
>> > +						     new_ssp,
>> > +						     &range))
>> 
>> Range isn't really needed by this function. I suggest changing
>> gdbarch_address_in_shadow_stack_memory_range to allow for it to be
>> nullptr and then pass nullptr here.
>
> I agree, fixed.
>
>> Also, the line above fits in 80 columns and doesn't need to be broken, even if
>> "&range" is changed to "nullptr".
>
> It is more than 80 columns for me.

Hm. When I edited it here and changed "&range" to "nullptr" the line
ended exactly at column 80. Which is arguably not ideal, so I don't mind
either way.

>> > +    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.  */
>> 
>> s/8 bytes/element size/
>
> Fixed.
>
>> 
>> > +  const int element_size
>> > +    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
>> > +
>> > +  const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>> > +
>> > +  write_memory_unsigned_integer (new_ssp, element_size, byte_order,
>> > +				 (ULONGEST) new_addr);
>> > +
>> > +  regcache_raw_write_unsigned (regcache,
>> > +			       gdbarch_ssp_regnum (gdbarch),
>> > +			       new_ssp);
>> 
>> The line above fits in 80 columns and doesn't need to be broken.
>
> I count 81 columns and there is also a soft limit of 74 characters:
>
> https://sourceware.org/legacy-ml/gdb-patches/2014-01/msg00216.html

Ah, I wasn't aware of the soft limit. Thanks for pointing it out.

> So I'll keep it as is, if that's fine for you.

Yes, of course.

>> > diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h new file mode
>> > 100644 index 00000000000..5c3ba80974e
>> > --- /dev/null
>> > +++ b/gdb/shadow-stack.h
>> > @@ -0,0 +1,39 @@
>> > +/* Definitions to manage a shadow stack pointer for GDB, the GNU debugger.
>> > +
>> > +   Copyright (C) 2024-2025 Free Software Foundation, Inc.
>> > +
>> > +   This file is part of GDB.
>> > +
>> > +   This program is free software; you can redistribute it and/or
>> > + modify
>> > +
>> > +   it under the terms of the GNU General Public License as published by
>> > +   the Free Software Foundation; either version 3 of the License, or
>> > +   (at your option) any later version.
>> > +
>> > +   This program is distributed in the hope that it will be useful,
>> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> > +   GNU General Public License for more details.
>> > +
>> > +   You should have received a copy of the GNU General Public License
>> > +   along with this program.  If not, see
>> > + <http://www.gnu.org/licenses/>.  */
>> > +
>> > +#ifndef GDB_SHADOW_STACK_H
>> > +#define GDB_SHADOW_STACK_H
>> > +
>> > +/* If shadow stack is enabled, push the address NEW_ADDR on the shadow
>> > +   stack and update the shadow stack pointer accordingly.  */
>> > +
>> > +void shadow_stack_push (gdbarch *gdbarch, regcache *regcache,
>> 
>> Recently, the project has been trying to make the header files contain all the
>> headers and definitions that they need, for the benefit of IDE and language
>> server users, so that these tools don't emit spurious errors when showing a
>> header file.
>
> Ah, ok I wasn't aware. Do you have a link for that ? I think I cannot follow 100 %.

There was a discussion about it in this thread:

https://sourceware.org/pipermail/gdb-patches/2024-February/206632.html

It resulted in this patch:

https://inbox.sourceware.org/gdb-patches/20240326190806.89541-4-simon.marchi@efficios.com/

And it's also in the wiki²:

  A .c, .cc or .h file should directly include the .h file of every
  declaration and/or definition it directly refers to. Exception: Do not
  include defs.h, server.h, common-defs.h directly.

-- 
Thiago

² https://sourceware.org/gdb/wiki/Internals%20GDB-C-Coding-Standards#Include_Files

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux.
  2025-09-23 11:18 ` [PATCH 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
@ 2025-11-26  4:22   ` Thiago Jung Bauermann
  0 siblings, 0 replies; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-11-26  4:22 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

> 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/next/x86/shstk.html.

The docs under "next/" are from the next tree, that is, patches that are
likely to be included in the next Linux version but not necessarily.

It's better to link to:

https://docs.kernel.org/arch/x86/shstk.html

which is the documentation that actually reached upstream.

> 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                        | 43 +++++++++++++++
>  .../amd64-shadow-stack-backtrace-signal.exp   | 54 +++++++++++++++++++
>  .../gdb.arch/amd64-shadow-stack-signal.c      | 31 +++++++++++
>  3 files changed, 128 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 f0db3b7a1b4..d72525a4cab 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -1952,6 +1952,46 @@ amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
>    return ssp;
>  }
>  
> +/* Return true, if FRAME is a valid shadow stack frame while FRAME.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.  */
> +
> +static bool
> +amd64_linux_is_no_return_shadow_stack_address
> +  (gdbarch *gdbarch, const shadow_stack_frame_info &frame)
> +{
> +  /* FRAME must be a valid shadow stack frame.  */
> +  std::pair<CORE_ADDR, CORE_ADDR> range;
> +  gdb_assert (gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> +							    frame.ssp,
> +							    &range));

The ssp member of a shadow_stack_frame_info object should always be in
the shadow stack memory range, no? This assert would be more effective
in that class' constructor.

> +  /* 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 ((frame.value & mask) == 0)
> +    return false;
> +
> +  /* 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 = (frame.value & (~mask));

Extraneous parentheses in the expression above.

> +  /* Compute the previous/old SSP.  The shadow stack grows downwards.  To
> +     compute the previous shadow stack pointer, we need to increment
> +     FRAME.SSP.  */
> +  CORE_ADDR prev_ssp
> +    = frame.ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch);
> +
> +  /* We incremented FRAME.SSP by one element to compute PREV_SSP before.
> +     In case FRAME.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);

It's better to use one gdb_assert per condition, so that if it triggers,
it's clear which condition was violated.

> +  return (shadow_stack_val_cleared == prev_ssp);

Extraneous parentheses in the expression above.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2025-09-23 11:18 ` [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
  2025-09-23 11:53   ` Eli Zaretskii
  2025-10-03 20:17   ` Tom Tromey
@ 2025-11-26  4:26   ` Thiago Jung Bauermann
  2026-01-22 17:01     ` Schimpe, Christina
  2 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-11-26  4:26 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

> +/* 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)
> +{
> +  /* There should either be low - high range, or no arguments.  */
> +  if ((argc != 0) && (argc != 2))
> +    error (_("-shadow-stack-list-frames: Usage: [FRAME_LOW FRAME_HIGH]"));
> +
> +  /* 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 = "-shadow-stack-list-frames: Invalid option " + err_str;
> +	  error (_("%s."), err_str.c_str ());

The call to _() here isn't very useful, because it puts the string "%s."
in the message file for translation:

#: gnu-nat.c:3090 mi/mi-cmd-stack.c:810
#, possible-c-format
msgid "%s."
msgstr ""

And we see from the comment that gnu-nat.c has the same problem. :)

Messages constructed piece by piece aren't translatable and so should be
avoided if possible.

> +	}
> +    }
> +  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);
> +
> +  if (!target_has_stack ())
> +    error (_("-shadow-stack-list-frames: No shadow stack."));
> +
> +  gdbarch *gdbarch = get_current_arch ();
> +  if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch))
> +    error (_("-shadow-stack-list-frames: Printing of shadow stack \
> +	      backtrace is not supported for the current target."));

The string above contains all the whitespace used for indentation. It
should be:

    error (_("-shadow-stack-list-frames: Printing of shadow stack "
	     "backtrace is not supported for the current target."));

Same for the other multi-line messages in this patch.

> +  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-list-frames: Shadow stack is not enabled for \
> +	      the current target."));

My comment on patch 6 about a similar message also applies here:

  At least for AArch64, GCS can be enabled or disabled per-thread so
  I would say "not enabled for the current thread" here.

> diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
> index 42032f51781..ef8e31c730e 100644
> --- a/gdb/shadow-stack.c
> +++ b/gdb/shadow-stack.c
> @@ -270,6 +270,12 @@ do_print_shadow_stack_frame_info
>        uiout->field_string
>  	("addr", hex_string_custom (frame.value, element_size * 2),
>  	 address_style.style ());
> +
> +      if (uiout->is_mi_like_p ())
> +	{
> +	  uiout->field_string
> +	    ("arch", (gdbarch_bfd_arch_info (gdbarch))->printable_name);

Extraneous parentheses around gdbarch_bfd_arch_info (gdbarch).

> +	}
>        uiout->text ("\n");
>        gdb_flush (gdb_stdout);
>        return;
> @@ -333,6 +339,12 @@ do_print_shadow_stack_frame_info
>  	    if (lib != nullptr)
>  	      print_lib (uiout, lib, true);
>  	  }
> +
> +	if (uiout->is_mi_like_p ())
> +	  {
> +	    uiout->field_string
> +	      ("arch", (gdbarch_bfd_arch_info (gdbarch))->printable_name);

Extraneous parentheses around gdbarch_bfd_arch_info (gdbarch).

> +	  }
>        } /* Extra scope to print frame tuple.  */
>  
>        uiout->text ("\n");

<snip>

> 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..219f1b8ad78
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp

My only suggestion for this testcase is to add "amd64" to the filename,
since it can only run on x86. Perhaps mi-amd64-shadow-stack-signal.exp?

> 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..bbbc7ec5460
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp

Samme suggestion here, about adding "amd64" to the filename.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-11-26  4:07       ` Thiago Jung Bauermann
@ 2025-11-26 16:29         ` Thiago Jung Bauermann
  2026-01-22 17:04           ` Schimpe, Christina
  2026-01-15 14:05         ` Schimpe, Christina
  1 sibling, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2025-11-26 16:29 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:

> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
>
>> And if not, do you have an idea how we can fix this? 
>
> I suggest adding a gdbarch method which given RANGE, returns how many
> entries there are in the shadow stack.

Actually I miswrote. The method shouldn't get the RANGE as argument
because then it wouldn't know where the stack ends. It should get the
addresses of the first and last elements in the shadow stack.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2025-10-31  0:47           ` Thiago Jung Bauermann
@ 2025-12-30 10:16             ` Schimpe, Christina
  2026-03-06  2:30               ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2025-12-30 10:16 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: 'gdb-patches@sourceware.org'

Hi Thiago,

apologies for the delayed feedback. Please find my comments below.

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Freitag, 31. Oktober 2025 01:47
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: 'gdb-patches@sourceware.org' <gdb-patches@sourceware.org>
> Subject: Re: [PATCH 0/9] Add new command to print the shadow stack
> backtrace
> 
> Hello Christina,
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> >> -----Original Message-----
> >> From: Schimpe, Christina
> >> Sent: Monday, October 13, 2025 8:35 AM
> >> To: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> Cc: gdb-patches@sourceware.org
> >> Subject: RE: [PATCH 0/9] Add new command to print the shadow stack
> >> backtrace
> >>
> >> > -----Original Message-----
> >> > From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> > Sent: Monday, October 13, 2025 3:19 AM
> >> > To: Schimpe, Christina <christina.schimpe@intel.com>
> >> > Cc: gdb-patches@sourceware.org
> >> > Subject: Re: [PATCH 0/9] Add new command to print the shadow stack
> >> > backtrace
> >> >
> >> > Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:
> >> >
> >> > > I tested your patches and there's a bug unfortunately:
>         ⋮
> >> > The problem turned out to be simple. I just had to set the regnum
> >> > for the GCSPR in the gdbarch:
> >> >
> >> > diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> >> > 95af82c26327..9e866fc319d4 100644
> >> > --- a/gdb/aarch64-tdep.c
> >> > +++ b/gdb/aarch64-tdep.c
> >> > @@ -4780,6 +4780,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);
> >> >
> >> > +  if (tdep->has_gcs ())
> >> > +    /* AArch64's shadow stack pointer is the GCSPR.  */
> >> > +    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
> >> > +
> >> >    /* ABI */
> >> >    set_gdbarch_short_bit (gdbarch, 16);
> >> >    set_gdbarch_int_bit (gdbarch, 32);
> >>
> >> Ah, I forgot about that. Cool that it works now. 😊
> >
> > I thought about this again. In addition to setting the regnum for the
> > shadow stack pointer, I would have expected that you also have to
> > implement the gdbarch hook top_addr_empty_shadow_stack to make GCS
> > work for shadow stack pointer unwinding and the shadow stack backtrace, or
> am I missing something?
> 
> Ah, I have a comment about this on my review of patch 1 that is sitting in my
> drafts folder. I was waiting to send them all together, but it's been taking me
> longer than I expected. I will just send what I have so far.
>
> But essentially, if there's no gdbarch_top_addr_empty_shadow_stack hook this
> patch series changes the check of whether ssp is empty from a <= comparison
> with the end of the range to a < comparison. Because of this,
> AArch64 doesn't need to implement the hook to make the existing aarch64-
> gcs*.exp tests work. It still needs it to make the "bt shadow"
> command work though.

I'll comment in the corresponding patch. Thanks a lot for catching that!
As you already commented,  the hook is required for amd64.

> > Both, the aarch64 implememtation for top_addr_empty_shadow_stack and
> > ssp regnum configuration, should then be part the first patch "Generalize
> handling ...".
> > Does that make sense to you?
> 
> Yes, patch 1 should include the changes needed to make the existing aarch64-
> gcs*.exp tests passing. As of v1, that's just the ssp regnum configuration.
> Depending on what you decide to do for v2, that could include the
> top_addr_empty_shadow_stack hook as well.
> 
> > Also I should probably add proper error messages when the
> > configuration of ssp_regnum is missing,
> 
> Is that possible? How would GDB distinguish between missing ssp regnum
> configuration versus the target not supporting shadow stacks?

I would add it to the error message that already exists for the absence of the hook
gdbarch_address_in_shadow_stack_memory_range:

--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -537,7 +537,8 @@ backtrace_shadow_command (const shadow_stack_print_options &print_options,
     error (_("No shadow stack."));
 
   gdbarch *gdbarch = get_current_arch ();
-  if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch))
+  if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
+      || gdbarch_ssp_regnum (gdbarch) == -1)
     error (_("Printing of the shadow stack backtrace is not supported for"
             " the current target."))

So the error message could mean both, either missing ssp regnum or the target
not supporting shadow stack. Later in that function we check if the shadow stack
is enabled for the current target.

Does that sound ok?

> > and improve the documentation for required gdbarch hooks to properly
> > implement "bt shadow" in gdbarch_components.py.
> 
> --
> Thiago

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer.
  2025-11-26  4:19       ` Thiago Jung Bauermann
@ 2025-12-30 10:39         ` Schimpe, Christina
  0 siblings, 0 replies; 67+ messages in thread
From: Schimpe, Christina @ 2025-12-30 10:39 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Mittwoch, 26. November 2025 05:19
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 1/9] gdb: Generalize handling of the shadow stack
> pointer.
> 
> Hello Christina,
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> > Thanks a lot for this detailed review!
> > I applied most of your comments, please find my feedback to your review
> below.
> 
> You're welcome!
> 
> >> -----Original Message-----
> >> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> Sent: Friday, 31 October 2025 02:32
> >> To: Schimpe, Christina <christina.schimpe@intel.com>
> >> Cc: gdb-patches@sourceware.org
> >> Subject: Re: [PATCH 1/9] gdb: Generalize handling of the shadow stack
> >> pointer.
> >>
> >> Christina Schimpe <christina.schimpe@intel.com> writes:
> >>
> >> > -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)
> >>
> >> IIUC, the '<=' comparison above isn't preserved by this patch. This
> >> function is replaced by dwarf2_prev_ssp, which uses
> >> gdbarch_address_in_shadow_stack_memory_range for this if condition,
> >> whose comparison in find_addr_mem_range is:
> >>
> >>       bool addr_in_mem_range
> >>         = (addr >= map.start_address && addr < map.end_address);
> >>
> >> Is this intended?
> >
> > Arg, thanks for catching that!
> >
> > I think I missed that because I introduced a typo/bug in the call
> >
> > 	      || gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> > 							       ssp,
> > 							       &range))
> >
> > which made the unwinding work properly in case of amd64.
> > However, the proper fix should be to pass new_ssp to
> > gdbarch_address_in_shadow_stack_memory_range
> > instead, and to implement gdbarch_top_addr_empty_shadow_stack also
> for amd64.
> >
> > Does that make sense?
> 
> Yes, I agree.
> 
> >> > -	    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;
> >> > -}
> >>
> >> <snip>
> >>
> >> > diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c new file mode
> >> > 100644 index 00000000000..d153d5fc846
> >> > --- /dev/null
> >> > +++ b/gdb/shadow-stack.c
> >> > @@ -0,0 +1,167 @@
> >> > +/* Manage a shadow stack pointer for GDB, the GNU debugger.
> >> > +
> >> > +   Copyright (C) 2024-2025 Free Software Foundation, Inc.
> >>
> >> Should this really start at 2024? According to Andrew Burgess¹:
> >
> > Yes, 2024 is correct in this case since our gdb-oneapi supported bt shadow
> since 2024.
> 
> Ah, right. Thanks for clarifying.
> 
> >> > +enum class ssp_update_direction
> >> > +{
> >> > +  /* Update ssp towards the bottom of the shadow stack.  */
> >> > +  bottom = 0,
> >> > +
> >> > +  /* Update ssp towards the top of the shadow stack.  */
> >> > +  top
> >> > +};
> >>
> >> I find the bottom/top nomenclature confusing, especially because it's
> >> supposed to mean the same thing whether the stack grows up or down.
> >> In my mind, if the stack grow down then top means "oldest element",
> >> but if the stack grows up, then top means "newest element".
> >> But in this patch it seems that top means "newest element" regardless
> >> of the direction of stack growth.
> >
> > Yes, that was my understanding. So independent in which direction a
> > shadow stack grows based on the architecture/OS, top always means
> > newest element.  But I think it is not a problem to take one of your
> suggestions.
> 
> Thank you. In my view it also matches the nomenclature in frame.h, which
> also doesn't use vertical concepts. E.g.,
> 
>   /* Given a FRAME, return the next (more inner, younger) or previous
>      (more outer, older) frame.  */
>   extern frame_info_ptr get_prev_frame (const frame_info_ptr &);
>   extern frame_info_ptr get_next_frame (const frame_info_ptr &);
> 
> >> I would suggest changing the enum names above to something that's not
> >> related to the vertical axis, so that their meaning will be clear
> >> regardless of which direction the stack grows. A few suggestions:
> >> shrink/grow, older/younger, outer/inner.
> >
> > I'd take outer/inner and describe it as follows:
> >
> > 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
> > };
> >
> > Is that understandable ?
> 
> Yes, thanks for making the change.
> 
> >> > +/* See shadow-stack.h.  */
> >> > +
> >> > +void shadow_stack_push (gdbarch *gdbarch, regcache *regcache,
> >>
> >> There's no need for a gdbarch argument. You can get it from the regcache.
> >
> > Fixed.
> >
> >> > +			const CORE_ADDR new_addr)
> >> > +{
> >> > +  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::top);
> >> > +
> >> > +  /* If NEW_SSP does not point to shadow stack memory, we assume
> the stack
> >> > +     is full.  */
> >> > +  std::pair<CORE_ADDR, CORE_ADDR> range;
> >> > +  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> >> > +						     new_ssp,
> >> > +						     &range))
> >>
> >> Range isn't really needed by this function. I suggest changing
> >> gdbarch_address_in_shadow_stack_memory_range to allow for it to be
> >> nullptr and then pass nullptr here.
> >
> > I agree, fixed.
> >
> >> Also, the line above fits in 80 columns and doesn't need to be
> >> broken, even if "&range" is changed to "nullptr".
> >
> > It is more than 80 columns for me.
> 
> Hm. When I edited it here and changed "&range" to "nullptr" the line ended
> exactly at column 80. Which is arguably not ideal, so I don't mind either way.
> 
> >> > +    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.  */
> >>
> >> s/8 bytes/element size/
> >
> > Fixed.
> >
> >>
> >> > +  const int element_size
> >> > +    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
> >> > +
> >> > +  const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> >> > +
> >> > +  write_memory_unsigned_integer (new_ssp, element_size, byte_order,
> >> > +				 (ULONGEST) new_addr);
> >> > +
> >> > +  regcache_raw_write_unsigned (regcache,
> >> > +			       gdbarch_ssp_regnum (gdbarch),
> >> > +			       new_ssp);
> >>
> >> The line above fits in 80 columns and doesn't need to be broken.
> >
> > I count 81 columns and there is also a soft limit of 74 characters:
> >
> > https://sourceware.org/legacy-ml/gdb-patches/2014-01/msg00216.html
> 
> Ah, I wasn't aware of the soft limit. Thanks for pointing it out.
> 
> > So I'll keep it as is, if that's fine for you.
> 
> Yes, of course.
> 
> >> > diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h new file mode
> >> > 100644 index 00000000000..5c3ba80974e
> >> > --- /dev/null
> >> > +++ b/gdb/shadow-stack.h
> >> > @@ -0,0 +1,39 @@
> >> > +/* Definitions to manage a shadow stack pointer for GDB, the GNU
> debugger.
> >> > +
> >> > +   Copyright (C) 2024-2025 Free Software Foundation, Inc.
> >> > +
> >> > +   This file is part of GDB.
> >> > +
> >> > +   This program is free software; you can redistribute it and/or
> >> > + modify
> >> > +
> >> > +   it under the terms of the GNU General Public License as published by
> >> > +   the Free Software Foundation; either version 3 of the License, or
> >> > +   (at your option) any later version.
> >> > +
> >> > +   This program is distributed in the hope that it will be useful,
> >> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> >> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> >> > +   GNU General Public License for more details.
> >> > +
> >> > +   You should have received a copy of the GNU General Public License
> >> > +   along with this program.  If not, see
> >> > + <http://www.gnu.org/licenses/>.  */
> >> > +
> >> > +#ifndef GDB_SHADOW_STACK_H
> >> > +#define GDB_SHADOW_STACK_H
> >> > +
> >> > +/* If shadow stack is enabled, push the address NEW_ADDR on the
> shadow
> >> > +   stack and update the shadow stack pointer accordingly.  */
> >> > +
> >> > +void shadow_stack_push (gdbarch *gdbarch, regcache *regcache,
> >>
> >> Recently, the project has been trying to make the header files
> >> contain all the headers and definitions that they need, for the
> >> benefit of IDE and language server users, so that these tools don't
> >> emit spurious errors when showing a header file.
> >
> > Ah, ok I wasn't aware. Do you have a link for that ? I think I cannot follow
> 100 %.
> 
> There was a discussion about it in this thread:
> 
> https://sourceware.org/pipermail/gdb-patches/2024-February/206632.html
> 
> It resulted in this patch:
> 
> https://inbox.sourceware.org/gdb-patches/20240326190806.89541-4-
> simon.marchi@efficios.com/
> 
> And it's also in the wiki²:
> 
>   A .c, .cc or .h file should directly include the .h file of every
>   declaration and/or definition it directly refers to. Exception: Do not
>   include defs.h, server.h, common-defs.h directly.
> 
> --
> Thiago
> 
> ² https://sourceware.org/gdb/wiki/Internals%20GDB-C-Coding-
> Standards#Include_Files

Hi Thiago,

Thank you for the feedback and sharing this.:)

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-11-26  4:07       ` Thiago Jung Bauermann
  2025-11-26 16:29         ` Thiago Jung Bauermann
@ 2026-01-15 14:05         ` Schimpe, Christina
  1 sibling, 0 replies; 67+ messages in thread
From: Schimpe, Christina @ 2026-01-15 14:05 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

HI Thiago, 

Please find my comments below.

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Mittwoch, 26. November 2025 05:07
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> > Hi Thiago,
> >
> > Thanks for taking the time to provide this review—I really appreciate your
> detailed input!
> 
> You're most welcome. Thank you for pushing the shadow stack feature
> forward!
> 
> > I’ve implemented part of your feedback, and there are still a few open
> points I’d like to clarify.
> > I hope I didn’t miss any of your comments, as the review has become quite
> comprehensive.
> 
> Indeed, the review ended up more detailed than I anticipated because I'm
> using the review process to also develop the AArch64 side. This made me dig
> into the code quite a bit.
> 
> > Please find my responses and open questions below. Looking forward to
> your thoughts!
> 
> Thanks!
> 
> >> -----Original Message-----
> >> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> Sent: Freitag, 31. Oktober 2025 05:02
> >> To: Schimpe, Christina <christina.schimpe@intel.com>
> >> Cc: gdb-patches@sourceware.org
> >> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the
> >> shadow stack backtrace.
> >>
> >> Christina Schimpe <christina.schimpe@intel.com> writes:
> >>
> >> > Example for the output of 'bt shadow' on amd64 linux:
> >> > ~~
> >> > (gdb) bt shadow
> >> > /#0  0x000000000040111f in call1 at amd64-shadow-stack.c:14
> >> > /#1  0x000000000040112f in main at amd64-shadow-stack.c:21
> >> > /#2  0x00007ffff7c3fe70 in __libc_start_call_main at
> >> > ../sysdeps/nptl/libc_start_call_main.h:58
> >> > /#3  0x00007ffff7c3ff20 in __libc_start_main_impl at
> >> > ../csu/libc-start.c:128
> >> > /#4  0x0000000000401045 in _start
> >> > ~~
> >>
> >> Here's an example output in my aarch64-linux test VM:
> >>
> >> (gdb) bt shadow
> >> #0  0x0000aaaaaaaa08ac in call2 at aarch64-gcs-return.c:65
> >> #1  0x0000aaaaaaaa08c0 in call1 at aarch64-gcs-return.c:71
> >> #2  0x0000aaaaaaaa09d4 in main at aarch64-gcs-return.c:110
> >> #3  0x0000000000000000 in ??
> >>
> >> And here is the regular backtrace at the same point:
> >>
> >> (gdb) bt
> >> #0  call3 () at aarch64-gcs-return.c:58
> >> #1  0x0000aaaaaaaa08ac in call2 () at aarch64-gcs-return.c:64
> >> #2  0x0000aaaaaaaa08c0 in call1 () at aarch64-gcs-return.c:70
> >> #3  0x0000aaaaaaaa09d4 in main () at aarch64-gcs-return.c:107
> >>
> >> I have a few comments on this output, at least as it is appears for
> >> GCS. I'm assuming x86 shadow stack output is similar:
> >>
> >> 1. The first thing that I notice is that the number of the frames
> >> don't match between the regular stack and the shadow stack: frame 0
> >> in the regular stack doesn't exist in the shadow stack, regular frame
> >> 1 is shadow frame 0, and so on.
> >>
> >> I think this is confusing. It makes sense that frame 0 doesn't appear
> >> in the shadow stack backtrace because there really isn't an entry for
> >> it in the shadow stack, but I think the shadow entries should start
> >> with number 1, to be consistent.
> >
> > Mh, it's probably better to align, I agree.
> > I'll go with starting frame #1 in the v2, and add a comment about that
> > in the commit message.

Thinking about this again I wonder if it's a good idea to start the numbering with #1. 
Besides frame #0, there are multiple reasons why the numbering of the normal backtrace
is out-of-sync with the shadow stack backtrace, e.g. inline calls, tail calls or python frame
filters.

Another option is to add the original #0 frame using the current $pc. But what I don’t like
about this, is that the user could be confused and think that $pc is on the shadow stack.

What do you think?

> >> 2. The line numbers corresponding to the return addresses don't match
> >> between the regular backtrace and the shadow backtrace, even though
> >> the return addresses are the same. I would say this is a bug. Perhaps
> >> there can be a test making sure they match, if it's not too complicated?
> >
> > The reason for that is explained in a comment in
> do_print_shadow_stack_frame_info:
> >
> >   /* In contrast to find_frame_sal which is used for the ordinary backtrace
> >      command, we always want to print the line that is actually referred
> >      to by the address in the linetable.  That's why we always pass 0 here
> >      as second argument.  */
>
> I don't understand why we want to always print the line that is actually
> referred to by the address in the linetable. find_frame_sal has this
> comment:
> 
>   /* If FRAME is not the innermost frame, that normally means that
>      FRAME->pc points at the return instruction (which is *after* the
>      call instruction), and we want to get the line containing the
>      call (because the call is where the user thinks the program is).
> 
> Doesn't that also apply to the addresses in the shadow stack?

Currently in v1 of this series, I print the line containing the return address
which results in the behaviour you describe in point 2 above: "The line
numbers corresponding to the return addresses don't match between the
regular backtrace and the shadow backtrace, ...".  If the user runs
"bt -shadow", I'd expect that he is familiar with the concept of shadow stacks
and I thought it would confuse him more if we show a line which is not a
return address.  But on the other hand, we want to align with the normal
backtrace and print the line containing the call...

> > Do you think we should align this with the behavior of the ordinary
> > backtrace command here as well?
> 
> I think so, but maybe there's something I'm not considering.

...but since the overall direction is to align more with the normal backtrace,
I agree with you here. 😊
In case the normal backtrace is broken due for instance a programming error
such as a buffer overflow the user can use "bt -shadow" as alternative.  In that case, 
implementing frame arguments would probably be helpful, too. But if we do this
afterwards, we'll change the default behavior of "bt -shadow". On the other hand,
this series is already big enough...
And, if we go that direction, I wonder if we shouldn't somehow construct frame #0
for the shadow stack. But as described above, I've also some concerns for that.

What do you think?
 
> > I’m not entirely sure if that approach might be too straightforward,
> > since we can’t simply call the get_frame_address_in_block function as
> > the ordinary bt command does when invoking it from find_frame_sal.
> 
> From what I understand of the big comment in get_frame_address_in_block,
> we can create an equivalent function for shadow stack frames: it should
> return an adjusted PC for frames corresponding to normal function calls,
> which are all except:
> 
> 1. sentinel frame
> 2. signal frame
> 3. dummy frame
> 
> These are easy to recognise (or in the case of 3, can be made easy to
> recognise) in the shadow stack:
> 
> 1. Since the shadow stack doesn't have frame #0, this_frame is never the
> sentinel one. And next_frame will be the "sentinel" if this_frame is the first
> one in the shadow stack.
> 2. The Linux kernel on both AArch64 and Intel puts a marker in the shadow
> stack when a signal is handled, so we can look for it in the shadow stack to
> recognize the signal frame.

Yes, this is what we already discussed here IIRC:
https://sourceware.org/pipermail/gdb-patches/2025-November/222588.html

I changed the gdbarch hook is_no_return_address to additionally take a string, 
which is configured to  <sigframe token> in case of signals for the amd64
implementation.

In the other thread you stated also: 
"In this example "<signal handler called>" message would replace the VDSO
function name in entry #0. Also note that it's an actual return address
but we still want to show a custom message for it."

I am not sure yet for x86 how to detect the shadow stack element for which you
want to display <signal handler called>, since this is a normal return address.
How do you plan to detect this for GCS ?

> 3. We can change GDB to also put a marker in the shadow stack when it does
> an inferior function call, and look for it in the shadow stack.

We call shadow_stack_push in generic GDB code. Do you think we could find an
architecture independent marker ?
Then we can also return true with the gdbarch hook is_no_return_address and
set the  string to <function called from gdb>.

I am just not sure if the linux kernel ever decides to extend its functionality for
CET shadow stack and uses the bits that we use for our marker.
In theory the linux kernel could decide to support 32bit shadow stack  for x86
one day, or supervisor shadow stacks – and I am not sure what bits the kernel
might set in the shadow stack elements to support this.
I wonder if we should discuss this in the linux kernel mailing lists.

But if we detect dummy and signal frames with the gdbarch hook is_no_return_address,
we should only call find_sal_for_pc for normal return addresses on the shadow stack. 

So I think it should be safe to simply call find_sal_for_pc as follows:
  symtab_and_line sal = find_sal_for_pc (frame.value, 1);

Or am I missing something ?

> >> > +/* 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_frame_sal).  Otherwise, we would have some PC range if
> >> > + the
> >>
> >> In the case of stack.c:frame_show_address, SAL comes from
> >> find_frame_sal, but in this case it comes from find_pc_line, right?
> >
> > Yes, it comes from find_sal_for_pc (when rebased on latest master,
> find_pc_line was renamed).
> >
> >> Does the comment still apply in this case?
> >
> > Good question, I remember I stumbled over this a couple of times when
> implementing this.
> > In any case, the comment needs to be corrected to "(SAL comes from
> find_sal_for_pc)."
> > If we need this check, I am not sure. In my experiments with inlined
> > functions I could not reproduce that scenario. But I kept it, to be safe.
> 
> From my reading of find_pc_line / find_sal_for_pc, it doesn't return a sal with
> line number but no PC. But I'm not very familiar with this part of the code.
> But I don't mind keeping this check (with an adjusted comment, as you
> mention).

Ok.

> >> > +     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; }
> 
> <snip>
> 
> >> > +/* Extract a char array which can be used for printing a reasonable
> >> > +   error message for REASON.  Note that in case REASON has the value
> >> > +   NO_ERROR the returned array is empty.  */
> >> > +
> >> > +static const char *
> >> > +ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason
> >> > +reason) {
> >> > +  switch (reason)
> >> > +    {
> >> > +    case ssp_unwind_stop_reason::no_error:
> >> > +      return _("");
> >>
> >> The empty string doesn't need to be translated
> >
> > True.
> >
> >> All callers make sure that reason isn't no_error, so perhaps just remove
> this case?
> >
> > I am not sure, if I can follow completely. You mean I should remove the case
> and replace
> >   gdb_assert_not_reached ("invalid reason"); with return "" ?
> 
> I mean just remove this case from the switch. Then this function will assert
> whenever reason != ssp_unwind_stop_reason::memory_read_error.

Ok, now the function looks as follows:

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.");
}

> >> > +    case ssp_unwind_stop_reason::memory_read_error:
> >> > +      return _("shadow stack memory read failure");
> >> > +    }
> >> > +
> >> > +  gdb_assert_not_reached ("invalid reason"); }
> 
> <snip>
> 
> >> > +/*  If possible, return a shadow stack frame info which is COUNT
> elements
> >> > +    above the bottom of the shadow stack.  FRAME should point to the
> top
> >> > +    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.
> >>
> >> Here is another place where I find "above/bottom/top" confusing,
> >> since stacks can grow up or grow down (as pointed out in the next
> >> comment in this function), and these terms mean different things in each
> case.
> >
> > Yes, the comment is now
> > "...above the outermost (oldest) element of the shadow stack."
> >
> > I hope this is more straight-forward.
> 
> There's still "above" and "top". A suggestion:
> 
> "If possible, return a shadow stack frame info which is COUNT elements
> newer than the outermost (oldest) element of the shadow stack.  FRAME
> should point to the newest element of the shadow stack..."

I missed to also update you on the following sentences :/ I'll change this comment
anyways again due to the discussion below. 

> 
> >> > +    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
> >> > +  (gdbarch *gdbarch, const std::pair<CORE_ADDR, CORE_ADDR> range,
> >> > +   const ULONGEST count, shadow_stack_frame_info &frame) {
> >> > +  /* Compute the number of bytes on the shadow stack, starting at
> >> > +     FRAME->SSP, which depends on the direction the shadow stack
> >> > +     grows.  */
> >> > +  const int element_size
> >> > +    = gdbarch_shadow_stack_element_size_aligned (gdbarch);
> >> > +  const unsigned long shadow_stack_bytes
> >> > +    = (gdbarch_stack_grows_down (gdbarch))
> >>
> >> The parentheses around the function call are superfluous.
> >
> > Fixed.
> >
> >> > +       ? range.second - frame.ssp : frame.ssp - range.first +
> >> > + element_size;
> >> > +
> >> > +  gdb_assert ((shadow_stack_bytes % element_size) == 0);  const
> >> > + unsigned long shadow_stack_size
> >> > +    = shadow_stack_bytes / element_size;
> >>
> >> This line fits 80 columns and doesn't need to be broken.
> >>
> >> > +  const long level = shadow_stack_size - count;
> >>
> >> In a comment further below you mention that level is 0-based, but
> >> doesn't this expression mean that it's 1-based? Perhaps it's missing
> >> a
> >> "- 1" term?

A shadow stack with 5 elements (shadow_stack_size = 5) has #0-#4 frames.
We call this function in case count is negative (bt -shadow -2) with its absolute
value so the calculation for level is 5-2 = 3.
This is the right level to start with for a shadow stack including frame #3 and #4.

~~~
(gdb) bt -shadow
#0  0x000055555555514a in call1 at amd64-shadow-stack.c:27
#1  0x000055555555515f in main at amd64-shadow-stack.c:38
#2  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
#3  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:360
#4  0x0000555555555065 in _start
(gdb) bt -shadow -2
#3  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:360
#4  0x0000555555555065 in _start 	
~~~

> >> Also, this doesn't work for GCS because it assumes all elements in
> >> the stack are return addresses. In GCS, the oldest element is a 0
> >> entry The 0 entry is why this patch requires AArch64 to implement its
> >> own gdbarch top_addr_empty_shadow_stack hook.
> >
> > There is clearly an issue with this function.  But I am not sure if I can follow
> here.
> > Shouldn't the issue be fixed when passing LEVEL instead of COUNT, as
> pointed out by you below?
> 
> Passing level instead of count is necessary but not sufficient.
> 
> The function's documentation comment says that it returns "a shadow stack
> frame info which is COUNT elements newer than the outermost
> (oldest) element of the shadow stack". One example:
> 
> Suppose that the shadow stack has 5 elements and count is 2. Then level as
> calculated by your patch is 3 (5 - 2). So frame.ssp will be adjusted by
> 3 elements by the call to update_shadow_stack_pointer further below. This is
> wrong though, because it makes new_ssp point to 1 element newer than the
> oldest element of the shadow stack.
> If level is calculated instead as level = shadow_stack_size - count - 1 then in
> this example it will be 2 and new_ssp will point to 2 elements newer than the
> oldest element of the shadow stack, which matches the documentation
> comment.

Ah, I think now I get your point.:)

We print the outermost 2 frames, as described in the docs
"With a negative COUNT, print outermost -COUNT frames." for "bt -shadow -2".

But the comment of the function get_trailing_outermost_shadow_stack_frame_info
does not match. 

"...return a shadow stack frame info which is COUNT elements
    above the outermost (oldest) element of the shadow stack."

Based on that for "bt -shadow -2",
~~~
#0  0x000055555555514a in call1 at amd64-shadow-stack.c:27
#1  0x000055555555515f in main at amd64-shadow-stack.c:38
#2  0x00007ffff7c2a1ca in __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
#3  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:360
#4  0x0000555555555065 in _start
~~~
we want to return the frame belonging to #3, so the frame which is COUNT-1 elements
above the oldest element of the shadow stack.

But still prefer to pass COUNT directly, without any further calculations to this function, 
similar as it's done for the normal backtrace in trailing_outermost_frame.  There the
comment is 

/* Return the starting frame needed to handle COUNT outermost frames.  */.

I found this comment a bit confusing back then when I implemented it, but now it makes
perfect sense to me.  I think I'll change it back to that one, does that sound ok to you ?
Now the full comment is:

/*  If possible, return the starting shadow stack frame info needed to handle
    COUNT outermost frames.  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.  */

> But that still doesn't work for AArch64, because every shadow stack there has
> an extra element at the beginning which also needs to be discounted, so in
> this case it should be:
> 
>   const long level = shadow_stack_size - count - 2;
> 
> But then it's wrong for Intel.
> 
> > And if not, do you have an idea how we can fix this?
> 
> I suggest adding a gdbarch method which given RANGE, returns how many
> entries there are in the shadow stack. And to calculate level as:
> 
>   const long level = shadow_stack_size - count - 1;
> 
> Where count is the result of the gdbarch hook.

I agree, I'll add a gdbarch hook for the shadow stack size.

> >> Finally, not a concern for userspace support (and thus this patch
> >> series) but for OS and hypervisor software there's another kind of
> >> GCS entry which is the exception return record that is put on the
> >> stack when an exception is taken. That entry is 32 bytes in size.  I
> >> mention this just to illustrate that calculating the number of
> >> entries in the stack can be non- trivial.
> >
> > Oh, I see. Isn't that already a problem with the current unwinding of
> > the shadow stack pointer for GCS? How would you fix it there ?
> 
> It's not a problem for now because GCS support is only implemented for
> Linux userspace inferiors. It's a bridge to be crossed in the future. :)
> 
> I just mentioned this case to illustrate that assuming that all entries in the
> shadow stack have the same size is fragile. But for now it's good enough, so
> perhaps I shouldn't have mentioned it.
> 
> >> Not sure how to account for these variations in generic code. Maybe
> >> add a new gdbarch method for returning the number of entries in the
> >> shadow stack?
> >
> > To implement this, I believe we have two possible approaches:
> >
> > (1) Assume we can determine the number of elements without unwinding
> > each frame (as it is currently the case). In this scenario, we could
> > introduce a generic gdbarch hook to retrieve the number of elements for
> architectures that require a different calculation.
> >
> > (2) Unwind each shadow stack frame to obtain the trailing outermost
> > frame, similar to how the normal backtrace works.
> >
> > Do you see any additional options?
> 
> No, I can also only think of these.
> 
> > Alternatively, we could keep the current approach for now (without the
> > gdbarch hook, based on the existing calculation) and only revise the
> > implementation if other OS or hypervisor software require different logic—
> once we have the ability to test those cases properly.
> > What are your thoughts?
> 
> The current approach isn't feasible because level needs to be calculated in
> one way for Intel, and a different way for AArch64. I suggest the gdbarch
> hook option.

Yes, I'll add the hook for the shadow stack size.

> >> > +
> >> > +  /* COUNT exceeds the number of elements on the shadow stack.
> Return the
> >> > +     starting shadow stack frame info FRAME.  */  if (level <= 0)
> >> > +    return std::optional<shadow_stack_frame_info> (frame);
> >> > +
> >> > +  CORE_ADDR new_ssp = update_shadow_stack_pointer
> >> > +    (gdbarch, frame.ssp, count, ssp_update_direction::bottom);
> >>
> >> Shouldn't update_shadow_stack_pointer be called with 'level' rather
> >> than 'count' as argument?
> >
> > Ah, you’re absolutely right. I’m certain this worked at some point before I
> posted it.
> > I’m not sure how this issue slipped in—and even more concerning that my
> tests didn’t catch it.
> >
> >> > +  if (gdbarch_stack_grows_down (gdbarch))
> >> > +    gdb_assert (new_ssp < range.second);  else
> >> > +    gdb_assert (new_ssp >= range.first);
> >> > +
> >> > +  CORE_ADDR new_value;
> >> > +  if (!read_shadow_stack_memory (gdbarch, new_ssp, &new_value,
> >> > +				 &frame.unwind_stop_reason))
> >> > +    return {};
> >> > +
> >> > +  return std::optional<shadow_stack_frame_info>
> >> > +    ({new_ssp, new_value, (ULONGEST) level,
> >> > +      ssp_unwind_stop_reason::no_error});
> >>
> >> This line causes a compilation error in arm-linux-gnueabihf, and I
> >> suppose other 32-bit targets as well:
> >>
> >> /home/bauermann/src/binutils-gdb/gdb/shadow-stack.c:471:27: error:
> >> narrowing conversion of ‘(ULONGEST)((long int)level)’ from ‘ULONGEST’
> >> {aka ‘long long unsigned int’} to ‘long unsigned int’ [-Werror=narrowing]
> >>   471 |     ({new_ssp, new_value, (ULONGEST) level,
> >> ssp_unwind_stop_reason::no_error});
> >>       |                           ^~~~~~~~~~~~~~~~
> >>
> >> Also, it fits 80 columns and doesn't need to be broken.
> >
> > Thanks, casting it as follows:
> > (unsigned long) level
> > should hopefully fix this. And then I will have more than 80 characters.
> 
> Ok.
> 
> >> > +}
> >> > +
> >> > +std::optional<shadow_stack_frame_info>
> >> > +shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info
> >> > +  (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range) {
> >> > +  /* 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 (gdbarch, this->ssp, 1,
> >> > +				   ssp_update_direction::bottom);
> >> > +
> >> > +  if (gdbarch_stack_grows_down (gdbarch))
> >>
> >> To make this work for GCS, I had to add another if statement before
> >> the one above, to handle the case where new_ssp is at the initial 0 value:
> >>
> >>   if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
> >>       && gdbarch_top_addr_empty_shadow_stack (gdbarch, new_ssp,
> range))
> >>     return {};
> >>   else if (gdbarch_stack_grows_down (gdbarch))
> >>     ⋮
> >>
> >> Otherwise "bt shadow" will print the 0 entry:
> >>
> >> (gdb) bt shadow
> >> #0  0x0000aaaaaaaa08ac in call2 at aarch64-gcs-return.c:65
> >> #1  0x0000aaaaaaaa08c0 in call1 at aarch64-gcs-return.c:71
> >> #2  0x0000aaaaaaaa09d4 in main at aarch64-gcs-return.c:110
> >> #3  0x0000000000000000 in ??
> >
> > Yes, I missed that. Thanks.
> >
> >> > +    {
> >> > +      /* The shadow stack grows downwards.  */
> >> > +      if (new_ssp >= range.second)
> >> > +	{
> >> > +	  /* We reached the bottom of the shadow stack.  */
> >>
> >> In this case, we reached the top actually. This is why I find it
> >> confusing to use bottom/top as if it where agnostic to whether the stack
> grows up or down...
> >
> > In my vision this is to "bottom" actually, if you look at this from a "stack"
> perspective.
> > But I changed this to "We reached the outermost element".
> 
> Thanks.
> 
> >> > +  /* 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.
> >> > + */
> >>
> >> Hm, I'm not sure if aarch64-linux supports GCS switching. The
> >> kernel's gcs.rst documentation says:
> >>
> >>   * The architecture provides instructions for switching between guarded
> >>     control stacks with checks to ensure that the new stack is a valid
> >>     target for switching.
> >>
> >> And a comment in AArch64's implementation of the map_shadow_stack
> >> syscall says:
> >>
> >>   /*
> >>    * Put a cap token at the end of the allocated region so it
> >>    * can be switched to.
> >>    */
> >>
> >> But I don't see anything positively mention that it's supported.
> >> I assume so, but I'll have to confirm. If that is the case, later I
> >> will submit a patch with any necessary changes.
> >
> > Ok, so keeping the current comment is fine for you?
> 
> Yes. Sorry, that was another confusing digression from me.

No worries. 😊

> >> > +  std::pair<CORE_ADDR, CORE_ADDR> range;
> >> > +  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> *start_ssp,
> >> > +						     &range))
> >>
> >> Shouldn't this if condition also check
> >> gdbarch_top_addr_empty_shadow_stack?
> >
> > For x86 this logic is fine, but I assume for GCS this does not work
> > for empty shadow stacks?
> 
> Indeed, we need the extra check for GCS.

The code in backtrace_shadow_command  now looks as follows:

[...]
  /* 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;
    }
[...]

Is that ok ?

> >> > +    {
> >> > +      /* If the current shadow stack pointer does not point to shadow
> >> > +	 stack memory, the shadow stack is empty.  */
> >> > +      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;
> >> > +  CORE_ADDR new_value;
> >> > +  if (read_shadow_stack_memory (gdbarch, *start_ssp, &new_value,
> &reason))
> >> > +    current = {*start_ssp, new_value, 0,
> >> > +	       ssp_unwind_stop_reason::no_error};
> >>
> >> This line fits in 80 columns and doesn't need to be broken.
> >
> > Yes, I changed that.
> >
> >> > +
> >> > +  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.  */
> >>
> >> Actually, trailing will be the frame above the one from which we
> >> should start printing. For example, if count is -3
> >> get_trailing_outermost_shadow_stack_frame_info will return the frame
> >> "which is COUNT elements above the bottom of the shadow stack", which
> >> in this case is the 4th frame counting from the oldest frame. But we
> >> need to start printing from the 3rd oldest frame ...
> >
> >> > +      if (count < 0)
> >> > +	{
> >> > +	  trailing = get_trailing_outermost_shadow_stack_frame_info
> >> > +		       (gdbarch, range, std::abs (count), *current);
> >>
> >> ... so this call should pass std::abs (count) - 1 as argument.
> >
> > Hm, I am not sure if I can follow. With the fix for passing level
> > instead of count my current output looks as follow:
> >
> > ~~~
> > Breakpoint 1, call2 () at /tmp/amd64-shadow-stack.c:21
> > 21        return 42; /* break call2.  */
> > (gdb) bt -past-main
> > #0  call2 () at /tmp /amd64-shadow-stack.c:21
> > #1  0x0000555555555142 in call1 () at /tmp/amd64-shadow-stack.c:27
> > #2  0x0000555555555153 in main () at /tmp/amd64-shadow-stack.c:38
> > #3  0x00007ffff7c2a1ca in __libc_start_call_main ([...]
> > #4  0x00007ffff7c2a28b in __libc_start_main_impl ([...]) at
> > ../csu/libc-start.c:360
> > #5  0x0000555555555065 in _start ()
> > (gdb) bt -past-main -3
> > #3  0x00007ffff7c2a1ca in __libc_start_call_main ([...]) at
> ../sysdeps/nptl/libc_start_call_main.h:58
> > #4  0x00007ffff7c2a28b in __libc_start_main_impl ([...])    at ../csu/libc-
> start.c:360
> > #5  0x0000555555555065 in _start ()
> > (gdb) bt shadow -3
> > #2  0x00007ffff7c2a1ca in __libc_start_call_main at
> > ../sysdeps/nptl/libc_start_call_main.h:74
> > #3  0x00007ffff7c2a28b in __libc_start_main_impl at
> > ../csu/libc-start.c:128
> > #4  0x0000555555555065 in _start
> > (gdb) bt shadow
> > #0  0x0000555555555142 in call1 at /tmp/amd64-shadow-stack.c:28
> > #1  0x0000555555555153 in main at /tmp/amd64-shadow-stack.c:39
> > #2  0x00007ffff7c2a1ca in __libc_start_call_main at
> > ../sysdeps/nptl/libc_start_call_main.h:74
> > #3  0x00007ffff7c2a28b in __libc_start_main_impl at
> > ../csu/libc-start.c:128
> > #4  0x0000555555555065 in _start
> > ~~~
> >
> > Which is correct from my perspective, but maybe I am missing something.
> > I think I lost the overview a bit, there are a number of issues in my
> > code here, unfortunately. :/ Note that for this output I still print the level
> starting at 0. Will apply this change once the other issues are clear.
> 
> I think this is working for you because of the off-by-one error in the
> calculation of level in get_trailing_outermost_shadow_stack_frame_info
> that I mentioned above. I think the off-by-one error here compensates the
> other one.

As I said, in the similar code in stack.c we also don't pass std::abs (count) - 1, but have this
code instead: "trailing = trailing_outermost_frame (-count);"

So I still think we should keep the current calculation and passing of count to
get_trailing_outermost_shadow_stack_frame_info, but I should adapt the
comment.


> >> > +    {
> >> > +      QUIT;
> >> > +
> >> > +      print_shadow_stack_frame_info (gdbarch, print_options, *current,
> >> > +				     LOCATION);
> >> > +
> >> > +      trailing = current;
> >> > +      current = current->unwind_prev_shadow_stack_frame_info
> (gdbarch,
> >> > +							      range);
> >>
> >> This line fits in 80 columns and doesn't need to be broken.
> >
> > Hm, weird, I again count more than 80 columns.
> 
> Strange indeed.
> 
> >> > +    }
> >> > +
> >> > +  /* If we've stopped before the end, mention that.  */  if
> >> > + (current && from_tty)
> >>
> >> While it's correct to use an std::optional in this way to check
> >> whether it has a value, IMHO it's clearer to be more explicit and use the
> has_value method.
> >> It's also consistent with the rest of the code in this function.
> >
> > I agree. I fixed that.
> >
> >> > +    gdb_printf (_("(More shadow stack frames follow...)\n"));
> >> > +
> >> > +  /* 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.has_value ()
> >>
> >> If I'm not mistaken, trailing.has_value () is always true at this point.
> >
> > I agree, this logic is from backtrace_command_1.
> > I think adding an assert here should be fine instead, so I suggest the
> following:
> >
> > ~~~
> > [...]
> >   /* 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.  */ [...] ~~~
> >
> > Please let me know if you think otherwise.
> 
> Looks good to me.
> 
> --
> Thiago

Thanks again for the feedback,

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2025-11-26  4:26   ` Thiago Jung Bauermann
@ 2026-01-22 17:01     ` Schimpe, Christina
  2026-03-06  2:44       ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2026-01-22 17:01 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

Hi Thiago, 

Thank you for the feedback!

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Mittwoch, 26. November 2025 05:27
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
> 
> Christina Schimpe <christina.schimpe@intel.com> writes:
> 
> > +/* 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)
> > +{
> > +  /* There should either be low - high range, or no arguments.  */
> > +  if ((argc != 0) && (argc != 2))
> > +    error (_("-shadow-stack-list-frames: Usage: [FRAME_LOW
> > +FRAME_HIGH]"));
> > +
> > +  /* 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 = "-shadow-stack-list-frames: Invalid option " + err_str;
> > +	  error (_("%s."), err_str.c_str ());
> 
> The call to _() here isn't very useful, because it puts the string "%s."
> in the message file for translation:
> 
> #: gnu-nat.c:3090 mi/mi-cmd-stack.c:810
> #, possible-c-format
> msgid "%s."
> msgstr ""
> 
> And we see from the comment that gnu-nat.c has the same problem. :)
> 
> Messages constructed piece by piece aren't translatable and so should be
> avoided if possible.
> 
> > +	}
> > +    }
> > +  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);
> > +
> > +  if (!target_has_stack ())
> > +    error (_("-shadow-stack-list-frames: No shadow stack."));
> > +
> > +  gdbarch *gdbarch = get_current_arch ();
> > +  if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch))
> > +    error (_("-shadow-stack-list-frames: Printing of shadow stack \
> > +	      backtrace is not supported for the current target."));
> 
> The string above contains all the whitespace used for indentation. It should
> be:
> 
>     error (_("-shadow-stack-list-frames: Printing of shadow stack "
> 	     "backtrace is not supported for the current target."));
> 
> Same for the other multi-line messages in this patch.
> 
> > +  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-list-frames: Shadow stack is not enabled for \
> > +	      the current target."));
> 
> My comment on patch 6 about a similar message also applies here:
> 
>   At least for AArch64, GCS can be enabled or disabled per-thread so
>   I would say "not enabled for the current thread" here.
> 
> > diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c index
> > 42032f51781..ef8e31c730e 100644
> > --- a/gdb/shadow-stack.c
> > +++ b/gdb/shadow-stack.c
> > @@ -270,6 +270,12 @@ do_print_shadow_stack_frame_info
> >        uiout->field_string
> >  	("addr", hex_string_custom (frame.value, element_size * 2),
> >  	 address_style.style ());
> > +
> > +      if (uiout->is_mi_like_p ())
> > +	{
> > +	  uiout->field_string
> > +	    ("arch", (gdbarch_bfd_arch_info (gdbarch))->printable_name);
> 
> Extraneous parentheses around gdbarch_bfd_arch_info (gdbarch).
> 
> > +	}
> >        uiout->text ("\n");
> >        gdb_flush (gdb_stdout);
> >        return;
> > @@ -333,6 +339,12 @@ do_print_shadow_stack_frame_info
> >  	    if (lib != nullptr)
> >  	      print_lib (uiout, lib, true);
> >  	  }
> > +
> > +	if (uiout->is_mi_like_p ())
> > +	  {
> > +	    uiout->field_string
> > +	      ("arch", (gdbarch_bfd_arch_info (gdbarch))->printable_name);
> 
> Extraneous parentheses around gdbarch_bfd_arch_info (gdbarch).
> 
> > +	  }
> >        } /* Extra scope to print frame tuple.  */
> >
> >        uiout->text ("\n");
> 
> <snip>
> 
> > 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..219f1b8ad78
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
> 
> My only suggestion for this testcase is to add "amd64" to the filename, since
> it can only run on x86. Perhaps mi-amd64-shadow-stack-signal.exp?
> 
> > 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..bbbc7ec5460
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
> 
> Samme suggestion here, about adding "amd64" to the filename.

I applied all of it except the filename, since for mi I don't see any file
starting with amd64-* in the testsuite folder gdb.mi/*.

What happens if you run this for GCS ? Could we create a source file
which is architecture independent?

Christina

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-11-26 16:29         ` Thiago Jung Bauermann
@ 2026-01-22 17:04           ` Schimpe, Christina
  2026-03-06  2:35             ` Thiago Jung Bauermann
  0 siblings, 1 reply; 67+ messages in thread
From: Schimpe, Christina @ 2026-01-22 17:04 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Mittwoch, 26. November 2025 17:30
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:
> 
> > "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> >
> >> And if not, do you have an idea how we can fix this?
> >
> > I suggest adding a gdbarch method which given RANGE, returns how many
> > entries there are in the shadow stack.
> 
> Actually I miswrote. The method shouldn't get the RANGE as argument
> because then it wouldn't know where the stack ends. It should get the
> addresses of the first and last elements in the shadow stack.
> 
> --
> Thiago

Hi Thiago, 

Hm, how would we know if we should pass the first or second value of range,
if we don't know which arch we're on?
We could examine in which direction the stack grows, but wouldn't it be safer
if we'd pass SSP and RANGE ? Maybe some arch needs all this information.

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2025-10-12 19:45     ` Schimpe, Christina
@ 2026-02-19 17:24       ` Tom Tromey
  2026-03-02 12:24         ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Tom Tromey @ 2026-02-19 17:24 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Tom Tromey, gdb-patches

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

> Hi Tom,
> Thank you for the feedback. 
> Please see my comment for annotations below.

Sorry about the delay in my response here.

> Alternatively, I see 2 options:
> -  I could add a check and in case of bt shadow not print annotations at all
> -  Remove annotations for the bt command completely

> I only found this comment in the docs:
> "The annotation mechanism has largely been superseded by GDB/MI (see GDB/MI)."
> https://sourceware.org/gdb/current/onlinedocs/gdb.html/Annotations.html#Annotations
> I am not sure if that means that they are deprecated or just rarely used. But maybe I am missing something.

> What do you think?

I didn't re-read the patch but essentially I would not bother adding
annotations to any new code.

Tom

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2026-02-19 17:24       ` Tom Tromey
@ 2026-03-02 12:24         ` Schimpe, Christina
  0 siblings, 0 replies; 67+ messages in thread
From: Schimpe, Christina @ 2026-03-02 12:24 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Donnerstag, 19. Februar 2026 18:24
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: Tom Tromey <tom@tromey.com>; gdb-patches@sourceware.org
> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
> stack backtrace.
> 
> >>>>> Schimpe, Christina <christina.schimpe@intel.com> writes:
> 
> > Hi Tom,
> > Thank you for the feedback.
> > Please see my comment for annotations below.
> 
> Sorry about the delay in my response here.
> 
> > Alternatively, I see 2 options:
> > -  I could add a check and in case of bt shadow not print annotations
> > at all
> > -  Remove annotations for the bt command completely
> 
> > I only found this comment in the docs:
> > "The annotation mechanism has largely been superseded by GDB/MI (see
> GDB/MI)."
> > https://sourceware.org/gdb/current/onlinedocs/gdb.html/Annotations.htm
> > l#Annotations I am not sure if that means that they are deprecated or
> > just rarely used. But maybe I am missing something.
> 
> > What do you think?
> 
> I didn't re-read the patch but essentially I would not bother adding
> annotations to any new code.

If I'll remove them and somebody would run the "bt -shadow" command,
he'd have annotations only partially as we'll miss the shadow stack specific parts,
for example, "shadow-stack-frame-begin".
And this is acceptable since we don't expect anybody to enable them. 

In that case I'll simply remove them.

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

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2025-12-30 10:16             ` Schimpe, Christina
@ 2026-03-06  2:30               ` Thiago Jung Bauermann
  2026-03-12  9:53                 ` Schimpe, Christina
  0 siblings, 1 reply; 67+ messages in thread
From: Thiago Jung Bauermann @ 2026-03-06  2:30 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: 'gdb-patches@sourceware.org'

Hello Christina,

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

> Hi Thiago,
>
> apologies for the delayed feedback. Please find my comments below.

Sorry for the even longer delay.

I said I'd provide my comments this week, but things didn't go exactly
as I planned and I will be out tomorrow so for now I will send what I
have — which isn't much.

I'll send additional comments next week. Especially regarding patch 6
where there are some open questions and comments. I'm still thinking
through them and experimenting.

>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Freitag, 31. Oktober 2025 01:47
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: 'gdb-patches@sourceware.org' <gdb-patches@sourceware.org>
>> Subject: Re: [PATCH 0/9] Add new command to print the shadow stack
>> backtrace
>> 
>> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
>> 
>> > Also I should probably add proper error messages when the
>> > configuration of ssp_regnum is missing,
>> 
>> Is that possible? How would GDB distinguish between missing ssp regnum
>> configuration versus the target not supporting shadow stacks?
>
> I would add it to the error message that already exists for the absence of the hook
> gdbarch_address_in_shadow_stack_memory_range:
>
> --- a/gdb/shadow-stack.c
> +++ b/gdb/shadow-stack.c
> @@ -537,7 +537,8 @@ backtrace_shadow_command (const shadow_stack_print_options &print_options,
>      error (_("No shadow stack."));
>  
>    gdbarch *gdbarch = get_current_arch ();
> -  if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch))
> +  if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
> +      || gdbarch_ssp_regnum (gdbarch) == -1)
>      error (_("Printing of the shadow stack backtrace is not supported for"
>              " the current target."))
>
> So the error message could mean both, either missing ssp regnum or the target
> not supporting shadow stack. Later in that function we check if the shadow stack
> is enabled for the current target.
>
> Does that sound ok?

Ah, I thought you wanted to have different error messages for each
case. Yes, this looks good to me.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace.
  2026-01-22 17:04           ` Schimpe, Christina
@ 2026-03-06  2:35             ` Thiago Jung Bauermann
  0 siblings, 0 replies; 67+ messages in thread
From: Thiago Jung Bauermann @ 2026-03-06  2:35 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

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

>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Mittwoch, 26. November 2025 17:30
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: gdb-patches@sourceware.org
>> Subject: Re: [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow
>> stack backtrace.
>> 
>> Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:
>> 
>> > "Schimpe, Christina" <christina.schimpe@intel.com> writes:
>> >
>> >> And if not, do you have an idea how we can fix this?
>> >
>> > I suggest adding a gdbarch method which given RANGE, returns how many
>> > entries there are in the shadow stack.
>> 
>> Actually I miswrote. The method shouldn't get the RANGE as argument
>> because then it wouldn't know where the stack ends. It should get the
>> addresses of the first and last elements in the shadow stack.
>> 
>> --
>> Thiago
>
> Hi Thiago, 
>
> Hm, how would we know if we should pass the first or second value of range,
> if we don't know which arch we're on?
> We could examine in which direction the stack grows, but wouldn't it be safer
> if we'd pass SSP and RANGE ? Maybe some arch needs all this information.
>

You're right. I see that's what you did in v2 with the
gdbarch_get_shadow_stack_size hook. Looks great to me. Thanks!

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2026-01-22 17:01     ` Schimpe, Christina
@ 2026-03-06  2:44       ` Thiago Jung Bauermann
  0 siblings, 0 replies; 67+ messages in thread
From: Thiago Jung Bauermann @ 2026-03-06  2:44 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

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

>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Mittwoch, 26. November 2025 05:27
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: gdb-patches@sourceware.org
>> Subject: Re: [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command
>> 
>> Christina Schimpe <christina.schimpe@intel.com> writes:
>> 
>> > 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..219f1b8ad78
>> > --- /dev/null
>> > +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
>> 
>> My only suggestion for this testcase is to add "amd64" to the filename, since
>> it can only run on x86. Perhaps mi-amd64-shadow-stack-signal.exp?
>> 
>> > 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..bbbc7ec5460
>> > --- /dev/null
>> > +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
>> 
>> Samme suggestion here, about adding "amd64" to the filename.
>
> I applied all of it except the filename, since for mi I don't see any file
> starting with amd64-* in the testsuite folder gdb.mi/*.
>
> What happens if you run this for GCS ? Could we create a source file
> which is architecture independent?

I tried that but didn't get very far. There is the tunable
"glibc.cpu.aarch64_gcs" which can be used to force GCS to be enabled,
but then GDB gets a SIGSEGV at startup if it's not itself built to use
GCS. I need to make the inferior use the prctl syscall to enable shadow
stack explicitly.

I assume this doesn't happen in your case because for Intel you need
both the tunable and the compiler flag "-fcf-protection=return" to
activate shadow stacks?

Speaking of tunables, now that I looked closer into the testcase I was
surprised that you need to explicitly enable the SHSTK bit in
glibc.cpu.hwcaps. 

On AArch64, if the CPU and the kernel both support GCS, then the GCS
hwcap will be automatically enabled. This doesn't mean that GCS will be
used by applications though.

Anyway, nothing to worry about. I'm just noting the difference.

My suggestion is to leave the testcase as you currently have it, then
I can adapt it later for AArch64 with a separate patch.

-- 
Thiago

^ permalink raw reply	[flat|nested] 67+ messages in thread

* RE: [PATCH 0/9] Add new command to print the shadow stack backtrace
  2026-03-06  2:30               ` Thiago Jung Bauermann
@ 2026-03-12  9:53                 ` Schimpe, Christina
  0 siblings, 0 replies; 67+ messages in thread
From: Schimpe, Christina @ 2026-03-12  9:53 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: 'gdb-patches@sourceware.org'

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Freitag, 6. März 2026 03:31
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: 'gdb-patches@sourceware.org' <gdb-patches@sourceware.org>
> Subject: Re: [PATCH 0/9] Add new command to print the shadow stack
> backtrace
> 
> Hello Christina,
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> > Hi Thiago,
> >
> > apologies for the delayed feedback. Please find my comments below.
> 
> Sorry for the even longer delay.
> 
> I said I'd provide my comments this week, but things didn't go exactly as I
> planned and I will be out tomorrow so for now I will send what I have —
> which isn't much.
> 
> I'll send additional comments next week. Especially regarding patch 6 where
> there are some open questions and comments. I'm still thinking through
> them and experimenting.

Hi Thiago, 

No worries about that. I'll wait for your full comments and then apply them. 
I have some other stuff to work on, too. :)

Thanks for all your review work!
Christina
Intel Deutschland GmbH
Registered Address: Dornacher Straße 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht München HRB 186928

^ permalink raw reply	[flat|nested] 67+ messages in thread

end of thread, other threads:[~2026-03-12  9:54 UTC | newest]

Thread overview: 67+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-09-23 11:18 [PATCH 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
2025-09-23 11:18 ` [PATCH 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
2025-10-31  1:31   ` Thiago Jung Bauermann
2025-11-17 11:18     ` Schimpe, Christina
2025-11-26  4:19       ` Thiago Jung Bauermann
2025-12-30 10:39         ` Schimpe, Christina
2025-09-23 11:18 ` [PATCH 2/9] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
2025-10-03 20:05   ` Tom Tromey
2025-09-23 11:18 ` [PATCH 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
2025-10-03 19:56   ` Tom Tromey
2025-09-23 11:18 ` [PATCH 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
2025-10-03 19:55   ` Tom Tromey
2025-09-23 11:18 ` [PATCH 5/9] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
2025-10-03 20:03   ` Tom Tromey
2025-09-23 11:18 ` [PATCH 6/9] gdb: Implement 'bt shadow' to print the shadow stack backtrace Christina Schimpe
2025-09-23 11:47   ` Eli Zaretskii
2025-09-25 11:06     ` Schimpe, Christina
2025-09-25 13:19       ` Eli Zaretskii
2025-09-25 14:58         ` Simon Marchi
2025-09-26  7:45           ` Schimpe, Christina
2025-10-29 15:05             ` Schimpe, Christina
2025-10-29 15:28               ` Guinevere Larsen
2025-11-03 19:47                 ` Schimpe, Christina
2025-11-04 11:53                   ` Guinevere Larsen
2025-11-05 16:33                     ` Schimpe, Christina
2025-10-13  1:17       ` Thiago Jung Bauermann
2025-10-13  7:19         ` Schimpe, Christina
2025-10-31  4:39           ` Thiago Jung Bauermann
2025-11-06 14:23             ` Schimpe, Christina
2025-10-03 20:15   ` Tom Tromey
2025-10-12 19:45     ` Schimpe, Christina
2026-02-19 17:24       ` Tom Tromey
2026-03-02 12:24         ` Schimpe, Christina
2025-10-31  4:02   ` Thiago Jung Bauermann
2025-11-17 20:14     ` Schimpe, Christina
2025-11-26  4:07       ` Thiago Jung Bauermann
2025-11-26 16:29         ` Thiago Jung Bauermann
2026-01-22 17:04           ` Schimpe, Christina
2026-03-06  2:35             ` Thiago Jung Bauermann
2026-01-15 14:05         ` Schimpe, Christina
2025-09-23 11:18 ` [PATCH 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
2025-09-23 11:49   ` Eli Zaretskii
2025-09-25 11:10     ` Schimpe, Christina
2025-11-02 21:20       ` Thiago Jung Bauermann
2025-11-12 17:28         ` Schimpe, Christina
2025-11-16 18:39           ` Thiago Jung Bauermann
2025-11-17 11:51             ` Schimpe, Christina
2025-09-23 11:18 ` [PATCH 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
2025-11-26  4:22   ` Thiago Jung Bauermann
2025-09-23 11:18 ` [PATCH 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
2025-09-23 11:53   ` Eli Zaretskii
2025-09-25 11:32     ` Schimpe, Christina
2025-10-03 20:17   ` Tom Tromey
2025-10-12 19:54     ` Schimpe, Christina
2025-10-13  0:06       ` Thiago Jung Bauermann
2025-11-26  4:26   ` Thiago Jung Bauermann
2026-01-22 17:01     ` Schimpe, Christina
2026-03-06  2:44       ` Thiago Jung Bauermann
2025-09-25 11:46 ` [PATCH 0/9] Add new command to print the shadow stack backtrace Schimpe, Christina
2025-10-08  1:46   ` Thiago Jung Bauermann
2025-10-13  1:18     ` Thiago Jung Bauermann
2025-10-13  6:34       ` Schimpe, Christina
2025-10-29 14:52         ` Schimpe, Christina
2025-10-31  0:47           ` Thiago Jung Bauermann
2025-12-30 10:16             ` Schimpe, Christina
2026-03-06  2:30               ` Thiago Jung Bauermann
2026-03-12  9:53                 ` Schimpe, Christina

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox