Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH v2 0/9] Add new command to print the shadow stack backtrace
@ 2026-01-23  8:05 Christina Schimpe
  2026-01-23  8:05 ` [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
                   ` (9 more replies)
  0 siblings, 10 replies; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

Hi all,

this is my v2 for the series
"Add new command to print the shadow stack backtrace".

v1 of this series can be found here:
https://sourceware.org/pipermail/gdb-patches/2025-September/221141.html

Not all opens are resolved, but this version already contains several
changes (and I'll be out for ~3 weeks), so I decided to post v2 anyway.

For the implementation of -past-main, I already have a prototype available,
but it's not ready yet to be included in this version.  I will add it in v3
of this series.

Changes since v1:
- Changed from the subcommand "bt shadow" to the command option "bt -shadow"
  The discussion for this decision is summarized here:
  https://sourceware.org/pipermail/gdb-patches/2025-November/222374.html.
- Changed the interface of the gdbarch hook is_no_return_shadow_stack_address,
  so that we can configure a string that should be displayed instead of the
  non-return address on the shadow stack.  For Intel CET, we now display
  <sigframe token> instead of the sigframe token on the shadow stack.
- A number of changes to make this command work with ARM GCS, as discussed
  with Thiago:
  * a new gdbarch hook get_shadow_stack_size, since for ARM'S GCS the
    calculation based on the shadow stack range is different
  * additional calls to the gdbarch hook top_addr_empty_shadow_stack in
    various locations.
- amd64 implementation for the gdbarch hook top_addr_empty_shadow_stack,
  since it turned out that we need it for CET shadow stacks as well. 
- Fixed a number of issues reported by Thiago, such as:
  * problems with a negative COUNT parameter (thanks again for catching
    that!)
  * issues with the target independent unwinding function for SSP
    (shadow-stack.c::dwarf2_prev_ssp).
- Changed the printing of line numbers so that it is now consistent with the
  normal backtrace.  Previously, we always printed the line corresponding to
  the return address; now we print the line containing the call.
- Added checks at the beginning of the command function to verify that all
  necessary gdbarch hooks required to print the shadow stack backtrace are
  implemented, so we now fail with an appropriate error message if any are
  missing.
- Updated the NEWS and documentation parts for the changes described above.

Opens:
1) Thiago suggested changing the frame numbering so that it always starts
at #1, since for the shadow stack we don't have frame #0 printed by the
normal backtrace.
2) Or, consider printing frame arguments and frame #0 similarly to what
the normal backtrace does.
3) For non-return addresses on the shadow stack, we want to display a string,
as already implemented for signals.  For inferior calls, we also want to
display <function called by GDB>.  The return address for inferior calls
is pushed onto the shadow stack by GDB, but we currently don’t have a way
to distinguish this address from normal return addresses.  Thiago suggested
pushing the return address together with a marker, but it’s still unclear
how this marker should look like.
4) For signals, we also want to print <signal handler called>, as in the
normal backtrace.  Since in this case we have a normal return address on
the shadow stack, it’s not yet clear to me how to implement this.
5) Remove annotations.  Based on Tom's input, I think we should drop them,
but I am not yet sure how exactly.  Please see my latest response here:
https://sourceware.org/pipermail/gdb-patches/2025-October/221652.html

My latest reply regarding items 1-4 can be found here:
https://sourceware.org/pipermail/gdb-patches/2026-January/224054.html

Note that this version is still breaking GCS support, since for patch #1,
I don't have the aarch64 implementation of top_addr_empty_shadow_stack
included.  It will be part of v3 of this series, once I receive Thiago's
input.

Thiago also indicated that he wants to introduce additional commands for GCS
"info shadow-stack enabled/locked".  However, for CET shadow stacks we
currently don’t see a need for this; please see my latest response on this
topic:
https://sourceware.org/pipermail/gdb-patches/2025-November/222408.html

This is an example shadow stack backtrace on amd64: 
~~~
(gdb) bt -shadow
#0  0x000055555555514a in call1 at tmp/amd64-shadow-stack.c:27
#1  0x000055555555515f in main at tmp/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
~~~

For comparison, this is the normal backtrace (with and without -past-main):
~~~
(gdb) bt
#0  call2 () at tmp/amd64-shadow-stack.c:21
#1  0x000055555555514a in call1 () at tmp/amd64-shadow-stack.c:27
#2  0x000055555555515f in main () at tmp/amd64-shadow-stack.c:38
(gdb) bt -past-main
#0  call2 () at tmp/amd64-shadow-stack.c:21
#1  0x000055555555514a in call1 () at tmp/amd64-shadow-stack.c:27
#2  0x000055555555515f in main () at tmp/amd64-shadow-stack.c:38
#3  0x00007ffff7c2a1ca in __libc_start_call_main (main=main@entry=0x55555555514c <main>, argc=argc@entry=1,
    argv=argv@entry=0x7fffffffe228) at ../sysdeps/nptl/libc_start_call_main.h:58
#4  0x00007ffff7c2a28b in __libc_start_main_impl (main=0x55555555514c <main>, argc=1, argv=0x7fffffffe228, init=<optimized out>,
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe218) at ../csu/libc-start.c:360
#5  0x0000555555555065 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: Add command option '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                                      |  13 +
 gdb/aarch64-linux-tdep.c                      |  51 +-
 gdb/aarch64-tdep.c                            |  46 +-
 gdb/amd64-linux-tdep.c                        | 188 +++---
 gdb/amd64-tdep.c                              |  20 +
 gdb/annotate.c                                |  93 ++-
 gdb/annotate.h                                |  18 +-
 gdb/doc/gdb.texinfo                           |  94 +++
 gdb/gdbarch-gen.c                             | 190 +++++-
 gdb/gdbarch-gen.h                             |  93 ++-
 gdb/gdbarch.h                                 |   1 +
 gdb/gdbarch_components.py                     | 120 +++-
 gdb/infcall.c                                 |   4 +-
 gdb/linux-tdep.c                              |   9 +-
 gdb/mi/mi-cmd-stack.c                         | 142 ++++
 gdb/mi/mi-cmds.c                              |   2 +
 gdb/mi/mi-cmds.h                              |   1 +
 gdb/shadow-stack.c                            | 633 ++++++++++++++++++
 gdb/shadow-stack.h                            |  95 +++
 gdb/stack.c                                   | 323 +++++----
 gdb/stack.h                                   |  55 ++
 .../amd64-shadow-stack-backtrace-signal.exp   |  49 ++
 .../gdb.arch/amd64-shadow-stack-cmds.exp      |  88 +++
 .../gdb.arch/amd64-shadow-stack-signal.c      |  31 +
 gdb/testsuite/gdb.base/options.exp            |   2 +-
 .../gdb.mi/mi-shadow-stack-signal.exp         |  69 ++
 gdb/testsuite/gdb.mi/mi-shadow-stack.exp      |  65 ++
 28 files changed, 2097 insertions(+), 400 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: 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] 49+ messages in thread

* [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-02-19 17:55   ` Tom Tromey
                     ` (2 more replies)
  2026-01-23  8:05 ` [PATCH v2 2/9] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
                   ` (8 subsequent siblings)
  9 siblings, 3 replies; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

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

Until now, handling of the shadow stack pointer has been done in the
target dependent implementations of the gdbarch hook
'gdbarch_shadow_stack_push'.  Also amd64 and aarch64 linux specific
unwinders for the shadow stack pointer are implemented.
In a following patch a command line option "-shadow" will be added to
the backtrace command to print the shadow stack backtrace.  This requires
more target-independent logic to handle the shadow stack pointer.  To
avoid that we duplicate the logic, add new source and header files
"shadow-stack" for the implementation of shadow_stack_push and shadow
stack pointer unwinding in a target-independent way.
---
 gdb/Makefile.in           |   2 +
 gdb/aarch64-linux-tdep.c  |  51 +-----------
 gdb/aarch64-tdep.c        |  46 +++--------
 gdb/amd64-linux-tdep.c    | 135 +++----------------------------
 gdb/amd64-tdep.c          |  20 +++++
 gdb/gdbarch-gen.c         | 126 +++++++++++++++++++++++------
 gdb/gdbarch-gen.h         |  63 +++++++++++----
 gdb/gdbarch_components.py |  81 +++++++++++++++----
 gdb/infcall.c             |   4 +-
 gdb/linux-tdep.c          |   9 ++-
 gdb/shadow-stack.c        | 166 ++++++++++++++++++++++++++++++++++++++
 gdb/shadow-stack.h        |  38 +++++++++
 12 files changed, 473 insertions(+), 268 deletions(-)
 create mode 100644 gdb/shadow-stack.c
 create mode 100644 gdb/shadow-stack.h

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


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

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

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

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

* [PATCH v2 2/9] gdb: Refactor 'stack.c:print_frame'.
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
  2026-01-23  8:05 ` [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-01-23  8:05 ` [PATCH v2 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

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

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

Approved-By: Tom Tromey <tom@tromey.com>
---
 gdb/stack.c | 90 +++++++++++++++++++++++++++++++++--------------------
 1 file changed, 57 insertions(+), 33 deletions(-)

diff --git a/gdb/stack.c b/gdb/stack.c
index 732d525083e..54935992ebf 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1320,6 +1320,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,
@@ -1365,12 +1419,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 (" (");
@@ -1403,28 +1453,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))
@@ -1434,12 +1463,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


[-- 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] 49+ messages in thread

* [PATCH v2 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument.
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
  2026-01-23  8:05 ` [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
  2026-01-23  8:05 ` [PATCH v2 2/9] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-01-23  8:05 ` [PATCH v2 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

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

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

Approved-By: Tom Tromey <tom@tromey.com>
---
 gdb/stack.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/gdb/stack.c b/gdb/stack.c
index 54935992ebf..ffca1cb0865 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -983,15 +983,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 (" [");
@@ -1000,6 +999,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


[-- 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] 49+ messages in thread

* [PATCH v2 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c.
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (2 preceding siblings ...)
  2026-01-23  8:05 ` [PATCH v2 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-02-19 17:32   ` Tom Tromey
  2026-01-23  8:05 ` [PATCH v2 5/9] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

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

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

diff --git a/gdb/stack.c b/gdb/stack.c
index ffca1cb0865..805142b2f9b 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1273,6 +1273,33 @@ get_last_displayed_sal ()
   return sal;
 }
 
+/* Find the function name for the symbol SYM.  */
+
+static gdb::unique_xmalloc_ptr<char>
+find_symbol_funname (const symbol *sym)
+{
+  gdb::unique_xmalloc_ptr<char> funname;
+  const char *print_name = sym->print_name ();
+
+  if (sym->language () == language_cplus)
+    {
+      /* It seems appropriate to use print_name () here,
+	 to display the demangled name that we already have
+	 stored in the symbol table, but we stored a version
+	 with DMGL_PARAMS turned on, and here we don't want to
+	 display parameters.  So remove the parameters.  */
+      funname = cp_remove_params (print_name);
+    }
+
+  if (funname == nullptr)
+    {
+      /* If we didn't hit the C++ case above, set *funname here.  */
+      funname = make_unique_xstrdup (print_name);
+    }
+
+  return funname;
+}
+
 
 /* Attempt to obtain the name, FUNLANG and optionally FUNCP of the function
    corresponding to FRAME.  */
@@ -1291,25 +1318,10 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang,
   func = get_frame_function (frame);
   if (func)
     {
-      const char *print_name = func->print_name ();
-
       *funlang = func->language ();
       if (funcp)
 	*funcp = func;
-      if (*funlang == language_cplus)
-	{
-	  /* It seems appropriate to use print_name() here,
-	     to display the demangled name that we already have
-	     stored in the symbol table, but we stored a version
-	     with DMGL_PARAMS turned on, and here we don't want to
-	     display parameters.  So remove the parameters.  */
-	  funname = cp_remove_params (print_name);
-	}
-
-      /* If we didn't hit the C++ case above, set *funname
-	 here.  */
-      if (funname == NULL)
-	funname = make_unique_xstrdup (print_name);
+      funname = find_symbol_funname (func);
     }
   else
     {
-- 
2.34.1


[-- 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] 49+ messages in thread

* [PATCH v2 5/9] gdb: Refactor 'stack.c:print_frame_info'.
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (3 preceding siblings ...)
  2026-01-23  8:05 ` [PATCH v2 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-01-23  8:05 ` [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

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

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

Approved-By: Tom Tromey <tom@tromey.com>
---
 gdb/stack.c | 159 +++++++++++++++++++++++++++++++---------------------
 1 file changed, 94 insertions(+), 65 deletions(-)

diff --git a/gdb/stack.c b/gdb/stack.c
index 805142b2f9b..0f8c52ecc93 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1019,6 +1019,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.
@@ -1035,8 +1115,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)
@@ -1107,75 +1185,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


[-- 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] 49+ messages in thread

* [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (4 preceding siblings ...)
  2026-01-23  8:05 ` [PATCH v2 5/9] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-01-23  8:52   ` Eli Zaretskii
                     ` (2 more replies)
  2026-01-23  8:05 ` [PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
                   ` (3 subsequent siblings)
  9 siblings, 3 replies; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

Add command option '-shadow" to the backtrace command to print the shadow
stack backtrace instead of the normal backtrace.

This option may be combined with “-frame-info” and implies ‘-no-filters’
and ‘-frame-arguments none’.  Also the options @samp{-past-main},
@samp{-past-entry} or @samp{-hide} are not supported.  So by default,
all available shadow stack frames are printed.

This is an example for the output of 'bt -shadow' on amd64 linux:
~~
(gdb) bt -shadow
/#0  0x000055555555514a in call1 at amd64-shadow-stack.c:27
/#1  0x000055555555515f in main at amd64-shadow-stack.c:38
/#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
~~~

Note that the normal backtrace includes one additional frame, since
the shadow stack backtrace relies on return addresses of the shadow
stack only.  But except the missing frame arguments and frame #0 of
the normal backtrace, the backtrace is the same.
~~~
(gdb) bt -past-main
/#0  call2 () at amd64-shadow-stack.c:21
/#1  0x000055555555514a in call1 () at amd64-shadow-stack.c:27
/#2  0x000055555555515f in main () at amd64-shadow-stack.c:38
/#3  0x00007ffff7c2a1ca in __libc_start_call_main (main=main@entry=0x55555555514c <main>, argc=argc@entry=1,
    argv=argv@entry=0x7fffffffe228) at ../sysdeps/nptl/libc_start_call_main.h:58
/#4  0x00007ffff7c2a28b in __libc_start_main_impl (main=0x55555555514c <main>, argc=1, argv=0x7fffffffe228, init=<optimized out>,
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe218) at ../csu/libc-start.c:360
/#5  0x0000555555555065 in _start ()
~~

This commit also adds a test for 'bt -shadow' on amd64.
Although the test is OS independent we can only test this on linux,
as GDB does not support shadow stack on other OS for now.
Also we do not add a test for 32 bit, as support for shadow stack is
limited to 64 bit by the linux kernel.
---
 gdb/NEWS                                      |   3 +
 gdb/amd64-linux-tdep.c                        |  26 +
 gdb/annotate.c                                |  93 +++-
 gdb/annotate.h                                |  18 +-
 gdb/doc/gdb.texinfo                           |  27 +
 gdb/gdbarch-gen.c                             |  32 ++
 gdb/gdbarch-gen.h                             |  15 +-
 gdb/gdbarch_components.py                     |  20 +-
 gdb/shadow-stack.c                            | 480 +++++++++++++++++-
 gdb/shadow-stack.h                            |   6 +
 gdb/stack.c                                   |  73 +--
 gdb/stack.h                                   |  55 ++
 .../gdb.arch/amd64-shadow-stack-cmds.exp      |  88 ++++
 gdb/testsuite/gdb.base/options.exp            |   2 +-
 14 files changed, 875 insertions(+), 63 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 74fc353d7e9..37b3add11ed 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 17
 
+* New "-shadow" command line option for the backtrace command to print the
+  shadow stack backtrace instead of the normal backtrace.
+
 * Support for .gdb_index sections with version less than 7 has been
   removed.
 
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index 656daa0f0ee..a4eabccf667 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -1963,6 +1963,29 @@ amd64_linux_top_addr_empty_shadow_stack
   return addr == range.second;
 }
 
+/* Return the number of elements which are currently on the shadow stack
+   based on the shadow stack memory RANGE [start_address, end_address)
+   of the current thread.  In case shadow stack is not enabled for the
+   current thread, return -1.  */
+
+static long
+amd64_linux_get_shadow_stack_size
+  (gdbarch *gdbarch,
+   const std::optional<CORE_ADDR> ssp,
+   const std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+  /* For x86, if we don't have a shadow stack pointer, we can assume
+     that the shadow stack is disabled for the current thread.  */
+  if (!ssp.has_value ())
+    return -1;
+
+  const unsigned long shadow_stack_bytes = range.second - *ssp;
+
+  gdb_assert ((shadow_stack_bytes % 8) == 0);
+
+  return shadow_stack_bytes / 8;
+}
+
 static void
 amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
 			     int num_disp_step_buffers)
@@ -2026,6 +2049,9 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
 
   set_gdbarch_top_addr_empty_shadow_stack
     (gdbarch, amd64_linux_top_addr_empty_shadow_stack);
+ 
+  set_gdbarch_get_shadow_stack_size
+    (gdbarch, amd64_linux_get_shadow_stack_size);
 }
 
 static void
diff --git a/gdb/annotate.c b/gdb/annotate.c
index 1f3a6d114b1..f8e91944094 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 1f1a5edfaa9..9b5944ee8f8 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 7059f73935c..f50d96a09f3 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8747,6 +8747,31 @@ Related setting: @ref{set print raw-frame-arguments}.
 @item -frame-info @code{auto}|@code{source-line}|@code{location}|@code{source-and-location}|@code{location-and-address}|@code{short-location}
 Set printing of frame information.
 Related setting: @ref{set print frame-info}.
+
+@item -shadow
+Print the shadow stack backtrace instead of the normal backtrace.
+Similar to the normal backtrace @value{GDBN} prints one line per
+element (so-called shadow stack frames) on the shadow stack.
+
+A shadow stack is supported, for instance, with the Intel Control-Flow
+Enforcement Technology (@xref{CET}) on x86 and the Guarded Control Stack
+feature (@xref{GCS}) on AArch64.
+
+This option may be combined with “-frame-info” and implies ‘-no-filters’
+and ‘-frame-arguments none’.  Also the options @samp{-past-main},
+@samp{-past-entry} or @samp{-hide} are not supported.  So by default,
+all available shadow stack frames are printed.
+
+This is how a shadow stack backtrace looks like on amd64:
+@smallexample
+@group
+#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 ../libc_main.h:58
+#3  0x00007ffff7c2a28b in __libc_start_main_impl at ../csu/libc-start.c:360
+#4  0x0000555555555065 in _start
+@end group
+@end smallexample
 @end table
 
 The optional @var{qualifier} is maintained for backward compatibility.
@@ -27160,6 +27185,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
@@ -27248,6 +27274,7 @@ registers
 
 @end itemize
 
+@node CET
 @subsubsection Intel Control-Flow Enforcement Technology.
 @cindex Intel Control-Flow Enforcement Technology.
 
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index 794bb4d50dc..be6570ad97d 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -262,6 +262,7 @@ struct gdbarch
   gdbarch_address_in_shadow_stack_memory_range_ftype *address_in_shadow_stack_memory_range = nullptr;
   gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr;
   int shadow_stack_element_size_aligned = 8;
+  gdbarch_get_shadow_stack_size_ftype *get_shadow_stack_size = nullptr;
 };
 
 /* Create a new ``struct gdbarch'' based on information provided by
@@ -535,6 +536,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of address_in_shadow_stack_memory_range, has predicate.  */
   /* Skip verify of top_addr_empty_shadow_stack, has predicate.  */
   /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0.  */
+  /* Skip verify of get_shadow_stack_size, has predicate.  */
   if (!log.empty ())
     internal_error (_("verify_gdbarch: the following are invalid ...%s"),
 		    log.c_str ());
@@ -1412,6 +1414,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_get_shadow_stack_size_p() = %d\n",
+	      gdbarch_get_shadow_stack_size_p (gdbarch));
+  gdb_printf (file,
+	      "gdbarch_dump: get_shadow_stack_size = <%s>\n",
+	      host_address_to_string (gdbarch->get_shadow_stack_size));
   if (gdbarch->dump_tdep != NULL)
     gdbarch->dump_tdep (gdbarch, file);
 }
@@ -5571,3 +5579,27 @@ set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch,
 {
   gdbarch->shadow_stack_element_size_aligned = shadow_stack_element_size_aligned;
 }
+
+bool
+gdbarch_get_shadow_stack_size_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->get_shadow_stack_size != NULL;
+}
+
+long
+gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch, const std::optional<CORE_ADDR> ssp, const std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->get_shadow_stack_size != NULL);
+  if (gdbarch_debug >= 2)
+    gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_size called\n");
+  return gdbarch->get_shadow_stack_size (gdbarch, ssp, range);
+}
+
+void
+set_gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch,
+				   gdbarch_get_shadow_stack_size_ftype get_shadow_stack_size)
+{
+  gdbarch->get_shadow_stack_size = get_shadow_stack_size;
+}
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index cbe970fd716..155d60b1b8b 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1785,9 +1785,11 @@ extern void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarc
      unwinding and inferior function calls
    - top_addr_empty_shadow_stack: required for shadow stack pointer unwinding
    - ssp_regnum: required for inferior function calls.
-
    If the shadow stack alignment is not the predefault of 8 bytes, configure
    the gdbarch value shadow_stack_element_size_aligned.
+   To support the command line option 'backtrace -shadow' in addition to all
+   values and methods listed above also the gdbarch hook get_shadow_stack_size,
+   has to be provided.
 
    If possible, return the shadow stack pointer.  If the shadow stack
    feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise
@@ -1830,3 +1832,14 @@ extern void set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *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);
+
+/* Return the number of elements which are currently on the shadow stack
+   based on the shadow stack pointer SSP and the shadow stack memory
+   RANGE [start_address, end_address) of the current thread.
+   In case shadow stack is not enabled for the current thread, return -1. */
+
+extern bool gdbarch_get_shadow_stack_size_p (struct gdbarch *gdbarch);
+
+typedef long (gdbarch_get_shadow_stack_size_ftype) (struct gdbarch *gdbarch, const std::optional<CORE_ADDR> ssp, const std::pair<CORE_ADDR, CORE_ADDR> range);
+extern long gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch, const std::optional<CORE_ADDR> ssp, const std::pair<CORE_ADDR, CORE_ADDR> range);
+extern void set_gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch, gdbarch_get_shadow_stack_size_ftype *get_shadow_stack_size);
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index d44e59a523d..1f24a3a6d72 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2830,9 +2830,11 @@ provided:
   unwinding and inferior function calls
 - top_addr_empty_shadow_stack: required for shadow stack pointer unwinding
 - ssp_regnum: required for inferior function calls.
-
 If the shadow stack alignment is not the predefault of 8 bytes, configure
 the gdbarch value shadow_stack_element_size_aligned.
+To support the command line option 'backtrace -shadow' in addition to all
+values and methods listed above also the gdbarch hook get_shadow_stack_size,
+has to be provided.
 
 If possible, return the shadow stack pointer.  If the shadow stack
 feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise
@@ -2893,3 +2895,19 @@ this value.
     predefault="8",
     invalid=False,
 )
+
+Method(
+    comment="""
+Return the number of elements which are currently on the shadow stack
+based on the shadow stack pointer SSP and the shadow stack memory
+RANGE [start_address, end_address) of the current thread.
+In case shadow stack is not enabled for the current thread, return -1.
+""",
+    type="long",
+    name="get_shadow_stack_size",
+    params=[
+        ("const std::optional<CORE_ADDR>", "ssp"),
+        ("const std::pair<CORE_ADDR, CORE_ADDR>", "range")
+    ],
+    predicate=True,
+)
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 77d7c07d970..82d98c7336c 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -23,8 +23,14 @@
 #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"
 
 class regcache;
+struct backtrace_cmd_options;
 
 enum class ssp_update_direction
 {
@@ -42,16 +48,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::outer
 		     : direction == ssp_update_direction::inner;
 
+  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.  */
@@ -71,7 +80,7 @@ void shadow_stack_push (regcache *regcache, const CORE_ADDR new_addr)
     return;
 
   const CORE_ADDR new_ssp
-    = update_shadow_stack_pointer (gdbarch, *ssp,
+    = update_shadow_stack_pointer (gdbarch, *ssp, 1,
 				   ssp_update_direction::inner);
 
   /* If NEW_SSP does not point to shadow stack memory, we assume the
@@ -132,7 +141,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::outer);
 
 	  /* On x86, if NEW_SSP points to the end outside of RANGE
@@ -164,3 +173,466 @@ dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache,
   retval->mark_bytes_unavailable (0, retval->type ()->length ());
   return retval;
 }
+
+/* Return true, if PC is in the middle of a statement.  Note that in the
+   middle of a statement PC range includes sal.end (SAL.PC, SAL.END].
+   Return false, if
+   - SAL.IS_STMT is false
+   - there is no location information associated with this SAL, which
+   could happen in case of inlined functions
+   - PC is not in the range (SAL.PC, SAL.END].
+   This function is similar to stack.c:frame_show_address but is used
+   to determine if we are in the middle of a statement only, not to decide
+   if we should print a frame's address.  */
+
+static bool
+pc_in_middle_of_statement (CORE_ADDR pc, symtab_and_line sal)
+{
+  if (sal.is_stmt == false)
+    return false;
+
+  /* If there is a line number, but no PC, then there is no location
+     information associated with this sal.  The only way that should
+     happen is for the call sites of inlined functions (SAL comes from
+     find_sal_for_pc).  Otherwise, we would have some PC range if the SAL
+     came from a line table.  However, as we don't have a frame for this
+     function we cannot assert (in contrast to frame_show_address).  */
+  if (sal.line != 0 && sal.pc == 0 && sal.end == 0)
+    return false;
+
+  return pc > sal.pc && pc <= sal.end;
+}
+
+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;
+};
+
+/* Attempt to obtain the function name based on the symbol of PC.  */
+
+static gdb::unique_xmalloc_ptr<char>
+find_pc_funname (CORE_ADDR pc)
+{
+  symbol *func = find_symbol_for_pc (pc);
+  if (func != nullptr)
+    return find_symbol_funname (func);
+
+  gdb::unique_xmalloc_ptr<char> funname;
+  bound_minimal_symbol msymbol = lookup_minimal_symbol_by_pc (pc);
+  if (msymbol.minsym != nullptr)
+    funname = make_unique_xstrdup (msymbol.minsym->print_name ());
+
+  return funname;
+}
+
+/* Print information of shadow stack frame info FRAME.  The output is
+   formatted according to PRINT_WHAT.  For the meaning of PRINT_WHAT, see
+   enum print_what comments in frame.h.  Note that PRINT_WHAT is overridden,
+   if PRINT_OPTIONS.print_frame_info != print_frame_info_auto.  */
+
+static void
+do_print_shadow_stack_frame_info
+  (ui_out *uiout, gdbarch *gdbarch,
+   const frame_print_options &fp_opts,
+   const shadow_stack_frame_info &frame, print_what print_what)
+{
+  if (fp_opts.print_frame_info != print_frame_info_auto)
+    {
+      /* Use the specific frame information desired by the user.  */
+      print_what
+	= *print_frame_info_to_print_what (fp_opts.print_frame_info);
+    }
+
+  /* In contrast to find_frame_sal which is used for the ordinary backtrace
+     command, PC always points at the return instruction (which is *after* the
+     call instruction).  Since we want to get the line containing the call
+     (because the call is where the user thinks the program is), we pass 1 here
+     as second argument.  */
+  symtab_and_line sal = find_sal_for_pc (frame.value, 1);
+
+  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)
+    {
+      bool mid_statement = pc_in_middle_of_statement (frame.value, sal);
+
+      /* While for the ordinary backtrace printing of pc is based on
+	 MID_STATEMENT determined by stack.c:frame_show_address and the
+	 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 frame_print_options &fp_opts,
+   const shadow_stack_frame_info &frame, print_what print_what)
+{
+  do_with_buffered_output
+    (do_print_shadow_stack_frame_info, current_uiout, gdbarch,
+     fp_opts, frame, print_what);
+}
+
+/* Extract a char array which can be used for printing a reasonable
+   error message for REASON.  REASON must not be NO_ERROR if passed
+   to this function.  */
+
+static const char *
+ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
+{
+  switch (reason)
+    {
+    case ssp_unwind_stop_reason::memory_read_error:
+      return _("shadow stack memory read failure");
+    }
+
+  gdb_assert_not_reached ("invalid unwind stop reason.");
+}
+
+/* Read the memory at shadow stack pointer SSP and assign it to
+   RETURN_VALUE.  In case 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 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.  */
+
+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)
+{
+  gdb_assert (gdbarch_get_shadow_stack_size_p (gdbarch));
+
+  const long shadow_stack_size
+    = gdbarch_get_shadow_stack_size (gdbarch,
+				     std::optional<CORE_ADDR> (frame.ssp),
+				     range);
+
+  /* We should only get here in case shadow stack is enabled for the
+     current thread.  */
+  gdb_assert (shadow_stack_size >= 0);
+
+  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, level, ssp_update_direction::outer);
+
+  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, (unsigned long) 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::outer);
+
+   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))
+    {
+      /* The shadow stack grows downwards.  */
+      if (new_ssp >= range.second)
+	{
+	  /* We reached the outermost element of the shadow stack.  */
+	  return {};
+	}
+      /* We updated new_ssp towards the outermost element of the shadow
+	 stack before, and new_ssp must be pointing to shadow stack
+	 memory.  */
+      gdb_assert (new_ssp > range.first);
+    }
+  else
+    {
+      /* The shadow stack grows upwards.  */
+      if (new_ssp < range.first)
+	{
+	  /* We reached the outermost element of the shadow stack.  */
+	  return {};
+	}
+      /* We updated new_ssp towards the outermost element of the shadow
+	 stack before, and new_ssp must be pointing to shadow stack
+	 memory.  */
+      gdb_assert (new_ssp <= range.second);
+    }
+
+  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.  */
+
+void
+backtrace_shadow_command (const frame_print_options &fp_opts,
+			  const char *count_exp, int from_tty)
+{
+  if (!target_has_stack ())
+    error (_("No shadow stack."));
+
+  gdbarch *gdbarch = get_current_arch ();
+  if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
+      || !gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
+      || !gdbarch_get_shadow_stack_size_p (gdbarch)
+      || gdbarch_ssp_regnum (gdbarch) == -1)
+    error (_("Printing of the shadow stack backtrace is not supported for"
+	     " the current target."));
+
+  regcache *regcache = get_thread_regcache (inferior_thread ());
+  bool shadow_stack_enabled = false;
+
+  std::optional<CORE_ADDR> start_ssp
+    = gdbarch_get_shadow_stack_pointer (gdbarch, regcache,
+					shadow_stack_enabled);
+
+  if (!start_ssp.has_value () || !shadow_stack_enabled)
+    error (_("Shadow stack is not enabled for the current thread."));
+
+  /* Check if START_SSP points to a shadow stack memory range and use
+     the returned range to determine when to stop unwinding.
+     Note that a shadow stack memory range can change, due to shadow stack
+     switches for instance on x86 for an inter-privilege far call or when
+     calling an interrupt/exception handler at a higher privilege level.
+     Shadow stack for userspace is supported for amd64 linux starting with
+     Linux kernel v6.6.  However, shadow stack switches are not supported
+     due to missing kernel space support.  We therefore implement this
+     command without support for shadow stack switches for now.  */
+  bool is_top_addr_empty_shadow_stack = false;
+  std::pair<CORE_ADDR, CORE_ADDR> range;
+  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
+						     &range))
+    {
+      /* For x86, if the current shadow stack pointer does not point to
+	 shadow stack memory but is valid, the shadow stack is empty.  */
+      is_top_addr_empty_shadow_stack = true;
+    }
+
+  if (!is_top_addr_empty_shadow_stack)
+    {
+      /* For ARM's Guarded Control Stack, the shadow stack can be empty
+	 even though START_SSP points to shadow stack memory range.  */
+      is_top_addr_empty_shadow_stack
+	= gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
+	  && gdbarch_top_addr_empty_shadow_stack (gdbarch, *start_ssp, range);
+    }
+
+  if (is_top_addr_empty_shadow_stack)
+    {
+      gdb_printf (_("The shadow stack is empty.\n"));
+      return;
+    }
+
+  /* Extract the first shadow stack frame info (level 0).  */
+  ssp_unwind_stop_reason reason = ssp_unwind_stop_reason::no_error;
+  std::optional<shadow_stack_frame_info> current;
+  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 ())
+    {
+      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;
+  for (current = trailing; current.has_value () && count != 0; count--)
+    {
+      QUIT;
+
+      print_shadow_stack_frame_info (gdbarch, fp_opts, *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.has_value () && from_tty)
+    gdb_printf (_("(More shadow stack frames follow...)\n"));
+
+  /* Due to the loop above, trailing always has a value at this point.  */
+  gdb_assert (trailing.has_value ());
+
+  /* If we've run out of shadow stack frames, and the reason appears to
+     be an error condition, print it.  */
+  if (!current.has_value ()
+      && trailing->unwind_stop_reason > ssp_unwind_stop_reason::no_error)
+    gdb_printf (_("Shadow stack backtrace stopped at shadow stack " \
+		  "pointer %s due to: %s.\n"),
+		paddress (gdbarch, trailing->ssp),
+		ssp_unwind_stop_reason_to_err_string
+		  (trailing->unwind_stop_reason));
+}
+
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 5f8395ec047..5370becfc9a 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -35,4 +35,10 @@ void shadow_stack_push (regcache *regcache, const CORE_ADDR new_addr);
 value *dwarf2_prev_ssp (const frame_info_ptr &this_frame,
 			void **this_cache, int regnum);
 
+/* Implementation of "backtrace shadow" comand.  */
+
+void backtrace_shadow_command
+  (const frame_print_options &fp_opts,
+   const char *count_exp, int from_tty);
+
 #endif /* GDB_SHADOW_STACK_H */
diff --git a/gdb/stack.c b/gdb/stack.c
index 0f8c52ecc93..83bd45506e2 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,
@@ -190,6 +191,7 @@ struct backtrace_cmd_options
   bool full = false;
   bool no_filters = false;
   bool hide = false;
+  bool shadow = false;
 };
 
 using bt_flag_option_def
@@ -213,6 +215,14 @@ static const gdb::option::option_def backtrace_command_option_defs[] = {
     [] (backtrace_cmd_options *opt) { return &opt->hide; },
     N_("Causes Python frame filter elided frames to not be printed."),
   },
+
+  bt_flag_option_def {
+    "shadow",
+    [] (backtrace_cmd_options *opt) { return &opt->shadow; },
+    N_("Print shadow stack frames instead of normal frames.\n\
+This option may be combined with “-frame-info” and\n\
+implies ‘-no-filters’ and ‘-frame-arguments none’."),
+  },
 };
 
 /* Prototypes for local functions.  */
@@ -968,11 +978,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++)
@@ -1022,7 +1030,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
@@ -1031,14 +1039,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)
@@ -1304,7 +1307,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;
@@ -1370,31 +1373,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 ())
@@ -1403,21 +1403,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 ());
@@ -2226,7 +2226,10 @@ backtrace_command (const char *arg, int from_tty)
   scoped_restore restore_set_backtrace_options
     = make_scoped_restore (&user_set_backtrace_options, set_bt_opts);
 
-  backtrace_command_1 (fp_opts, bt_cmd_opts, arg, from_tty);
+  if (!bt_cmd_opts.shadow)
+    backtrace_command_1 (fp_opts, bt_cmd_opts, arg, from_tty);
+  else
+    backtrace_shadow_command (fp_opts, arg, from_tty);
 }
 
 /* Completer for the "backtrace" command.  */
diff --git a/gdb/stack.h b/gdb/stack.h
index ad2700b59a7..d62607ba361 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 e4daecb590d..c547dc4e49a 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 frame1 "#0\[ \t\]*$sspval_call2 in call1$fill"
+    set frame2 "#1\[ \t\]*$sspval_call1 in main$fill"
+    set frame3 "#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 \
+	    "$frame1" \
+	    "$frame2" \
+	    "$frame3" \
+	    ".*" ] \
+	"test shadow stack backtrace until the bottom of the stack."
+
+    gdb_test "bt -shadow 2" \
+	[multi_line \
+	    "$frame1" \
+	    "$frame2" \
+	    "\\(More shadow stack frames follow...\\)" ] \
+	"test shadow stack backtrace with a positive value for count"
+
+    # We can only test that we print a single frame, as the shadow stack
+    # enablement might depend on the underlying OS.
+    gdb_test "bt -shadow -1" "^#$decimal\[ \t\]*$hex$fill" \
+	"test shadow stack backtrace with a negative value for count"
+
+    # Test backtrace limit
+    gdb_test_no_output "set backtrace limit 2"
+    gdb_test "bt -shadow" \
+	[multi_line \
+	    "$frame1" \
+	    "$frame2" ] \
+	"test shadow stack backtrace with limit"
+    gdb_test_no_output "set backtrace limit unlimited" "restore backtrace limit default"
+
+    # 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/options.exp b/gdb/testsuite/gdb.base/options.exp
index 0b8dda51d4d..c5097f46b15 100644
--- a/gdb/testsuite/gdb.base/options.exp
+++ b/gdb/testsuite/gdb.base/options.exp
@@ -286,7 +286,6 @@ proc_with_prefix test-backtrace {} {
     clean_restart
 
     test_gdb_complete_unique "backtrace" "backtrace"
-    test_gdb_complete_none "backtrace "
 
     gdb_test "backtrace -" "Ambiguous option at: -"
     gdb_test "backtrace --" "No stack\\."
@@ -302,6 +301,7 @@ proc_with_prefix test-backtrace {} {
 	"-past-entry"
 	"-past-main"
 	"-raw-frame-arguments"
+	"-shadow"
     }
 
     # Test that we complete the qualifiers, if there's any.
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Dornacher 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] 49+ messages in thread

* [PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (5 preceding siblings ...)
  2026-01-23  8:05 ` [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-01-23  8:47   ` Eli Zaretskii
  2026-02-19 17:41   ` Tom Tromey
  2026-01-23  8:05 ` [PATCH v2 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
                   ` (2 subsequent siblings)
  9 siblings, 2 replies; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

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

On x86 with CET or on ARM with GCS, there can be elements on the shadow
stack which are not return addresses.  In this case, we don't want to print
the shadow stack element, but a string instead which describes the frame
similar to the normal backtrace command for dummy frames or signals.

Provide a gdbarch hook to distinguish between return and non-return
addresses and to configure a string which is printed instead of the
shadow stack element.
---
 gdb/doc/gdb.texinfo       | 20 +++++++++++++
 gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
 gdb/gdbarch-gen.h         | 17 +++++++++++
 gdb/gdbarch.h             |  1 +
 gdb/gdbarch_components.py | 21 +++++++++++++
 gdb/shadow-stack.c        | 63 ++++++++++++++-------------------------
 gdb/shadow-stack.h        | 40 +++++++++++++++++++++++++
 7 files changed, 154 insertions(+), 40 deletions(-)

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index f50d96a09f3..f7dc7c4397d 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8772,6 +8772,26 @@ This is how a shadow stack backtrace looks like on amd64:
 #4  0x0000555555555065 in _start
 @end group
 @end smallexample
+
+There can be elements on the shadow stack which are not return addresses,
+for example on x86 with the Intel Control-Flow Enforcement Technology
+(@xref{CET}).  In case of signals, the old shadow stack pointer is pushed
+in a special format with bit 63 set.  See @url{https://docs.kernel.org/arch/x86/shstk.html)}
+for more details.  For such shadow stack elements, the shadow stack
+frame just contains the level and a string describing the shadow stack
+element:
+
+@smallexample
+@group
+(gdb) bt -shadow 4
+#0  0x00007ffff7c45330 in __restore_rt from /lib/x86_64-linux-gnu/libc.so.6
+#1  <sigframe token>
+#2  0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:26
+#3  0x000055555555519d in main at tmp/amd64-shadow-stack-signal.c:29
+(More shadow stack frames follow...)
+@end group
+@end smallexample
+
 @end table
 
 The optional @var{qualifier} is maintained for backward compatibility.
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index be6570ad97d..76e841698af 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -263,6 +263,7 @@ struct gdbarch
   gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr;
   int shadow_stack_element_size_aligned = 8;
   gdbarch_get_shadow_stack_size_ftype *get_shadow_stack_size = nullptr;
+  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
@@ -537,6 +538,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of top_addr_empty_shadow_stack, has predicate.  */
   /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0.  */
   /* Skip verify of get_shadow_stack_size, has predicate.  */
+  /* 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 ());
@@ -1420,6 +1422,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
   gdb_printf (file,
 	      "gdbarch_dump: get_shadow_stack_size = <%s>\n",
 	      host_address_to_string (gdbarch->get_shadow_stack_size));
+  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);
 }
@@ -5603,3 +5611,27 @@ set_gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch,
 {
   gdbarch->get_shadow_stack_size = get_shadow_stack_size;
 }
+
+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, std::string &frame_type)
+{
+  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, frame_type);
+}
+
+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 155d60b1b8b..e24a5415ce0 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1843,3 +1843,20 @@ extern bool gdbarch_get_shadow_stack_size_p (struct gdbarch *gdbarch);
 typedef long (gdbarch_get_shadow_stack_size_ftype) (struct gdbarch *gdbarch, const std::optional<CORE_ADDR> ssp, const std::pair<CORE_ADDR, CORE_ADDR> range);
 extern long gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch, const std::optional<CORE_ADDR> ssp, const std::pair<CORE_ADDR, CORE_ADDR> range);
 extern void set_gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch, gdbarch_get_shadow_stack_size_ftype *get_shadow_stack_size);
+
+/* 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 options '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.  In this case,
+   also the string frame_type has to be configured to display the type in the
+   shadow stack backtrace. */
+
+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, std::string &frame_type);
+extern bool gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const shadow_stack_frame_info &frame, std::string &frame_type);
+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 fe59846b916..6e966e12f21 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 1f24a3a6d72..d39cba6e016 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2911,3 +2911,24 @@ In case shadow stack is not enabled for the current thread, return -1.
     ],
     predicate=True,
 )
+
+Method(
+    comment="""
+There can be elements on the shadow stack which are not return addresses.
+This happens for example on x86 with CET in case of signals.
+If an architecture implements the command options '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.  In this case,
+also the string frame_type has to be configured to display the type in the
+shadow stack backtrace.
+""",
+    type="bool",
+    name="is_no_return_shadow_stack_address",
+    params=[
+        ("const shadow_stack_frame_info &", "frame"),
+        ("std::string &", "frame_type")],
+    predicate=True,
+)
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 82d98c7336c..e930187604e 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -203,46 +203,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;
-};
-
 /* Attempt to obtain the function name based on the symbol of PC.  */
 
 static gdb::unique_xmalloc_ptr<char>
@@ -271,6 +231,29 @@ do_print_shadow_stack_frame_info
    const frame_print_options &fp_opts,
    const shadow_stack_frame_info &frame, print_what print_what)
 {
+  std::string frame_type;
+  if (gdbarch_is_no_return_shadow_stack_address_p (gdbarch)
+      && gdbarch_is_no_return_shadow_stack_address (gdbarch,
+						    frame,
+						    frame_type))
+    {
+      gdb_assert (!frame_type.empty ());
+
+      /* 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);
+
+      uiout->field_string
+	("func", frame_type, metadata_style.style ());
+      uiout->text ("\n");
+      gdb_flush (gdb_stdout);
+      return;
+    }
+
   if (fp_opts.print_frame_info != print_frame_info_auto)
     {
       /* Use the specific frame information desired by the user.  */
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 5370becfc9a..24a6d48efa0 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -41,4 +41,44 @@ void backtrace_shadow_command
   (const frame_print_options &fp_opts,
    const char *count_exp, int from_tty);
 
+enum class ssp_unwind_stop_reason
+{
+  /* No particular reason; either we haven't tried unwinding yet, or we
+     didn't fail.  */
+  no_error = 0,
+
+  /* We could not read the memory of the shadow stack element.  */
+  memory_read_error
+};
+
+/* Information of a shadow stack frame belonging to a shadow stack element
+   at shadow stack pointer SSP.  */
+
+class shadow_stack_frame_info
+{
+public:
+  /* If possible, unwind the previous shadow stack frame info.  RANGE is
+     the shadow stack memory range [start_address, end_address) belonging
+     to this frame's shadow stack pointer.  If we cannot unwind the
+     previous frame info, set the unwind_stop_reason attribute.  If we
+     reached the bottom of the shadow stack just don't return a value.  */
+  std::optional<shadow_stack_frame_info> unwind_prev_shadow_stack_frame_info
+    (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


[-- 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] 49+ messages in thread

* [PATCH v2 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux.
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (6 preceding siblings ...)
  2026-01-23  8:05 ` [PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-02-19 17:43   ` Tom Tromey
  2026-01-23  8:05 ` [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
  2026-03-02 12:39 ` [PATCH v2 0/9] Add new command to print the shadow stack backtrace Schimpe, Christina
  9 siblings, 1 reply; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

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

There can be elements on the shadow stack which are not return addresses.
This can happen, for instance, in case of signals on amd64 linux.
The old shadow stack pointer is pushed in a special format with bit 63 set.

|1...old SSP| - Pointer to old pre-signal ssp in sigframe token format
                (bit 63 set to 1)

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

Implement the gdbarch hook is_no_return_shadow_stack_address to detect
this scenario to print the shadow stack backtrace correctly.
---
 gdb/amd64-linux-tdep.c                        | 55 ++++++++++++++++++-
 .../amd64-shadow-stack-backtrace-signal.exp   | 49 +++++++++++++++++
 .../gdb.arch/amd64-shadow-stack-signal.c      | 31 +++++++++++
 3 files changed, 134 insertions(+), 1 deletion(-)
 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 a4eabccf667..b517f9772f5 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -1986,6 +1986,56 @@ amd64_linux_get_shadow_stack_size
   return shadow_stack_bytes / 8;
 }
 
+/* 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.  In case this is true, configure the string
+   which describes the frame and is displayed instead of the address in
+   the shadow stack backtrace.  */
+
+static bool
+amd64_linux_is_no_return_shadow_stack_address
+  (gdbarch *gdbarch,
+   const shadow_stack_frame_info &frame,
+   std::string &frame_type)
+{
+  /* 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);
+
+  if (shadow_stack_val_cleared == prev_ssp)
+    {
+      frame_type = _("<sigframe token>");
+      return true;
+    }
+
+  return false;
+}
+
 static void
 amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
 			     int num_disp_step_buffers)
@@ -2049,9 +2099,12 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
 
   set_gdbarch_top_addr_empty_shadow_stack
     (gdbarch, amd64_linux_top_addr_empty_shadow_stack);
- 
+
   set_gdbarch_get_shadow_stack_size
     (gdbarch, amd64_linux_get_shadow_stack_size);
+
+  set_gdbarch_is_no_return_shadow_stack_address
+    (gdbarch, amd64_linux_is_no_return_shadow_stack_address);
 }
 
 static void
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
new file mode 100644
index 00000000000..21373dc07f3
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
@@ -0,0 +1,49 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test shadow stack backtrace for signal handling on linux.
+
+require allow_ssp_tests {istarget "*-*-linux*"}
+
+standard_testfile amd64-shadow-stack-signal.c
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+
+    append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+    if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+	  {debug additional_flags="-fcf-protection=return"}] } {
+	return
+    }
+
+    if { ![runto_main] } {
+	return
+    }
+
+    gdb_breakpoint "handler"
+    gdb_test "continue" \
+	".*Program received signal SIGUSR1, User defined signal 1.*" \
+	"continue until signal"
+    gdb_continue_to_breakpoint "continue to breakpoint in handler"
+
+    # Test shadow stack backtrace including <sigframe token>.
+    gdb_test "bt -shadow" \
+	[multi_line \
+	    "#0\[ \t\]*$hex in \[^\r\n\]+" \
+	    "#1\[ \t\]*<sigframe token>" \
+	    "#2\[ \t\]*$hex in \[^\r\n\]+" \
+	    ".*" ] \
+	"test shadow stack backtrace for signal handling."
+}
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c b/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c
new file mode 100644
index 00000000000..c726e05b224
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c
@@ -0,0 +1,31 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2024-2026 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <signal.h>
+
+void
+handler (int signo)
+{
+}
+
+int
+main (void)
+{
+  signal (SIGUSR1, handler);
+  raise (SIGUSR1);
+  return 0;
+}
-- 
2.34.1


[-- 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: 387 bytes --]

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

* [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (7 preceding siblings ...)
  2026-01-23  8:05 ` [PATCH v2 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
@ 2026-01-23  8:05 ` Christina Schimpe
  2026-01-23  8:46   ` Eli Zaretskii
  2026-02-19 18:26   ` Tom Tromey
  2026-03-02 12:39 ` [PATCH v2 0/9] Add new command to print the shadow stack backtrace Schimpe, Christina
  9 siblings, 2 replies; 49+ messages in thread
From: Christina Schimpe @ 2026-01-23  8:05 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

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

Add the mi command for the command "backtrace -shadow".
Similar to the mi interface for the normal 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                         | 142 ++++++++++++++++++
 gdb/mi/mi-cmds.c                              |   2 +
 gdb/mi/mi-cmds.h                              |   1 +
 gdb/shadow-stack.c                            |  18 ++-
 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, 362 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 37b3add11ed..d3991a495f4 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -177,6 +177,16 @@ qExecAndArgs
      deprecated since GDB 10.  Users who need to control the size of a
      memory port's internal buffer can use the 'setvbuf' procedure.
 
+* 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 f7dc7c4397d..829b58a62f0 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -35433,6 +35433,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 f2a4b3044b0..a7a50da9f48 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,142 @@ 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 thread."));
+
+  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."));
+
+  /* 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, user_frame_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 552eafbbcbc..f823ff3b238 100644
--- a/gdb/mi/mi-cmds.c
+++ b/gdb/mi/mi-cmds.c
@@ -303,6 +303,8 @@ add_builtin_mi_commands ()
   add_mi_cmd_mi ("stack-info-frame", mi_cmd_stack_info_frame);
   add_mi_cmd_mi ("stack-list-arguments", mi_cmd_stack_list_args);
   add_mi_cmd_mi ("stack-list-frames", mi_cmd_stack_list_frames);
+  add_mi_cmd_mi ("shadow-stack-list-frames",
+		 mi_cmd_shadow_stack_list_frames);
   add_mi_cmd_mi ("stack-list-locals", mi_cmd_stack_list_locals);
   add_mi_cmd_mi ("stack-list-variables", mi_cmd_stack_list_variables);
   add_mi_cmd_mi ("stack-select-frame", mi_cmd_stack_select_frame,
diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h
index 81c4f0b4e37..2a37bd8861a 100644
--- a/gdb/mi/mi-cmds.h
+++ b/gdb/mi/mi-cmds.h
@@ -100,6 +100,7 @@ extern mi_cmd_argv_ftype mi_cmd_stack_list_frames;
 extern mi_cmd_argv_ftype mi_cmd_stack_list_locals;
 extern mi_cmd_argv_ftype mi_cmd_stack_list_variables;
 extern mi_cmd_argv_ftype mi_cmd_stack_select_frame;
+extern mi_cmd_argv_ftype mi_cmd_shadow_stack_list_frames;
 extern mi_cmd_argv_ftype mi_cmd_symbol_list_lines;
 extern mi_cmd_argv_ftype mi_cmd_symbol_info_functions;
 extern mi_cmd_argv_ftype mi_cmd_symbol_info_module_functions;
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index e930187604e..6a440bc5379 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -249,6 +249,13 @@ do_print_shadow_stack_frame_info
 
       uiout->field_string
 	("func", frame_type, metadata_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;
@@ -312,6 +319,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");
@@ -334,10 +347,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 frame_print_options &fp_opts,
    const shadow_stack_frame_info &frame, print_what print_what)
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 24a6d48efa0..ae5adc6fb01 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -81,4 +81,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 frame_print_options &fp_opts,
+   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..c84f96ce37b
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
@@ -0,0 +1,69 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test the mi command -shadow-stack-list-frames for signal handling on linux.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+require allow_ssp_tests {istarget "*-*-linux*"}
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+    append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+    set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack-signal.c"
+    set testfile mi-shadow-stack
+
+    # Test shadow-stack-list-frames for shadow stack element which is no
+    # return address.
+    if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+	{debug additional_flags="-fcf-protection=return"}] } {
+	return
+    }
+
+    if { [mi_clean_restart $testfile] } {
+	return
+    }
+
+    mi_runto_main
+    mi_send_resuming_command "exec-continue" "continue till signal"
+
+    set r_signal "reason=\"signal-received\",signal-name=\"SIGUSR1\",signal-meaning=\"User defined signal 1\""
+    gdb_expect {
+	-re ".*stopped,${r_signal}.*$mi_gdb_prompt" {
+	    pass "Wait for user interrupt"
+	}
+	timeout {
+	    fail "Wait for user interrupt (timeout)"
+	    return
+	}
+    }
+
+    mi_gdb_test "break handler" \
+	{(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done}
+
+    mi_execute_to "exec-continue" "breakpoint-hit" "handler" ".*" ".*" ".*" \
+	{"" "disp=\"keep\""} "continue to handler"
+
+    # We only test the frame belonging to the shadow stack element which
+    # is not a return address.  This frame is 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\",func=\"<sigframe token>\",arch=\"$any\"\}\\\]" \
+	"test shadow-stack-list-frames"
+
+    mi_gdb_exit
+}
diff --git a/gdb/testsuite/gdb.mi/mi-shadow-stack.exp b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
new file mode 100644
index 00000000000..ad92f21cf3e
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
@@ -0,0 +1,65 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test the mi command -shadow-stack-list-frames.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+require allow_ssp_tests
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+    append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+    set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack.c"
+    set testfile mi-shadow-stack
+
+    if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+	  {debug additional_flags="-fcf-protection=return"}] } {
+	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


[-- 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: 387 bytes --]

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

* Re: [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2026-01-23  8:05 ` [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
@ 2026-01-23  8:46   ` Eli Zaretskii
  2026-02-13 19:17     ` Schimpe, Christina
  2026-02-19 18:26   ` Tom Tromey
  1 sibling, 1 reply; 49+ messages in thread
From: Eli Zaretskii @ 2026-01-23  8:46 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

> From: Christina Schimpe <christina.schimpe@intel.com>
> Cc: thiago.bauermann@linaro.org
> Date: Fri, 23 Jan 2026 08:05:31 +0000
> 
>  gdb/NEWS                                      |  10 ++
>  gdb/doc/gdb.texinfo                           |  47 ++++++
>  gdb/mi/mi-cmd-stack.c                         | 142 ++++++++++++++++++
>  gdb/mi/mi-cmds.c                              |   2 +
>  gdb/mi/mi-cmds.h                              |   1 +
>  gdb/shadow-stack.c                            |  18 ++-
>  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, 362 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 37b3add11ed..d3991a495f4 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -177,6 +177,16 @@ qExecAndArgs
>       deprecated since GDB 10.  Users who need to control the size of a
>       memory port's internal buffer can use the 'setvbuf' procedure.
>  
> +* 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.
> +

This part is okay.

> +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
                     ^^^^^^^^^^^^^^^^^^
This should be in @code.

> +If invoked without arguments, this command prints a backtrace for the
> +whole shadow stack.  Like the -stack-list-frames command, if given two
                                 ^^^^^^^^^^^^^^^^^^
Likewise.

> +For frame 1 we can see that we only print @var{level}, @var{addr} and
> +@var{arch}.

Using "we" in the manual is not the best way of expressing such stuff.
How about

  Note that for frame 1 @value{GDBN} printed only @var{level},
  @var{addr}, and @var{arch}.

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

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

* Re: [PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2026-01-23  8:05 ` [PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
@ 2026-01-23  8:47   ` Eli Zaretskii
  2026-02-19 17:41   ` Tom Tromey
  1 sibling, 0 replies; 49+ messages in thread
From: Eli Zaretskii @ 2026-01-23  8:47 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

> From: Christina Schimpe <christina.schimpe@intel.com>
> Cc: thiago.bauermann@linaro.org
> Date: Fri, 23 Jan 2026 08:05:29 +0000
> 
>  gdb/doc/gdb.texinfo       | 20 +++++++++++++
>  gdb/gdbarch-gen.c         | 32 ++++++++++++++++++++
>  gdb/gdbarch-gen.h         | 17 +++++++++++
>  gdb/gdbarch.h             |  1 +
>  gdb/gdbarch_components.py | 21 +++++++++++++
>  gdb/shadow-stack.c        | 63 ++++++++++++++-------------------------
>  gdb/shadow-stack.h        | 40 +++++++++++++++++++++++++
>  7 files changed, 154 insertions(+), 40 deletions(-)

Thanks, the gdb.texinfo part is okay.

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

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

* Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-01-23  8:05 ` [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
@ 2026-01-23  8:52   ` Eli Zaretskii
  2026-02-13 16:42     ` Schimpe, Christina
  2026-02-19 18:19   ` Tom Tromey
  2026-03-06  4:31   ` Thiago Jung Bauermann
  2 siblings, 1 reply; 49+ messages in thread
From: Eli Zaretskii @ 2026-01-23  8:52 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

> From: Christina Schimpe <christina.schimpe@intel.com>
> Cc: thiago.bauermann@linaro.org
> Date: Fri, 23 Jan 2026 08:05:28 +0000
> 
>  gdb/NEWS                                      |   3 +
>  gdb/amd64-linux-tdep.c                        |  26 +
>  gdb/annotate.c                                |  93 +++-
>  gdb/annotate.h                                |  18 +-
>  gdb/doc/gdb.texinfo                           |  27 +
>  gdb/gdbarch-gen.c                             |  32 ++
>  gdb/gdbarch-gen.h                             |  15 +-
>  gdb/gdbarch_components.py                     |  20 +-
>  gdb/shadow-stack.c                            | 480 +++++++++++++++++-
>  gdb/shadow-stack.h                            |   6 +
>  gdb/stack.c                                   |  73 +--
>  gdb/stack.h                                   |  55 ++
>  .../gdb.arch/amd64-shadow-stack-cmds.exp      |  88 ++++
>  gdb/testsuite/gdb.base/options.exp            |   2 +-
>  14 files changed, 875 insertions(+), 63 deletions(-)

Thanks.

> diff --git a/gdb/NEWS b/gdb/NEWS
> index 74fc353d7e9..37b3add11ed 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,9 @@
>  
>  *** Changes since GDB 17
>  
> +* New "-shadow" command line option for the backtrace command to print the
> +  shadow stack backtrace instead of the normal backtrace.
> +
>  * Support for .gdb_index sections with version less than 7 has been
>    removed.

This part is okay.

> +A shadow stack is supported, for instance, with the Intel Control-Flow
> +Enforcement Technology (@xref{CET}) on x86 and the Guarded Control Stack
> +feature (@xref{GCS}) on AArch64.
            ^^^^^^^^^^
This should use @pxref, not @xref.

> +This option may be combined with “-frame-info” and implies ‘-no-filters’
> +and ‘-frame-arguments none’.     ^^^^^^^^^^^^^             ^^^^^^^^^^^^^
       ^^^^^^^^^^^^^^^^^^^^^^^
All those should use @code and without the quotes.

> +@node GCS
>  @subsubsection AArch64 Guarded Control Stack
>  @cindex Guarded Control Stack, AArch64
>  @cindex GCS, AArch64
> @@ -27248,6 +27274,7 @@ registers
>  
>  @end itemize
>  
> +@node CET
>  @subsubsection Intel Control-Flow Enforcement Technology.
>  @cindex Intel Control-Flow Enforcement Technology.

You are adding @node's, but AFAICT you don't change the @menu of the
parent section.  That can only work with recent Texinfo versions,
where @menus are updated automatically.  Please update the parent
@menu as part of the patch, to not rely on those recent versions.

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

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

* RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-01-23  8:52   ` Eli Zaretskii
@ 2026-02-13 16:42     ` Schimpe, Christina
  2026-04-14  8:43       ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-02-13 16:42 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Eli Zaretskii <eliz@gnu.org>
> Sent: Freitag, 23. Januar 2026 09:52
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print
> the shadow stack backtrace.
> 
> > From: Christina Schimpe <christina.schimpe@intel.com>
> > Cc: thiago.bauermann@linaro.org
> > Date: Fri, 23 Jan 2026 08:05:28 +0000
> >
> >  gdb/NEWS                                      |   3 +
> >  gdb/amd64-linux-tdep.c                        |  26 +
> >  gdb/annotate.c                                |  93 +++-
> >  gdb/annotate.h                                |  18 +-
> >  gdb/doc/gdb.texinfo                           |  27 +
> >  gdb/gdbarch-gen.c                             |  32 ++
> >  gdb/gdbarch-gen.h                             |  15 +-
> >  gdb/gdbarch_components.py                     |  20 +-
> >  gdb/shadow-stack.c                            | 480 +++++++++++++++++-
> >  gdb/shadow-stack.h                            |   6 +
> >  gdb/stack.c                                   |  73 +--
> >  gdb/stack.h                                   |  55 ++
> >  .../gdb.arch/amd64-shadow-stack-cmds.exp      |  88 ++++
> >  gdb/testsuite/gdb.base/options.exp            |   2 +-
> >  14 files changed, 875 insertions(+), 63 deletions(-)
> 
> Thanks.
> 
> > diff --git a/gdb/NEWS b/gdb/NEWS
> > index 74fc353d7e9..37b3add11ed 100644
> > --- a/gdb/NEWS
> > +++ b/gdb/NEWS
> > @@ -3,6 +3,9 @@
> >
> >  *** Changes since GDB 17
> >
> > +* New "-shadow" command line option for the backtrace command to
> > +print the
> > +  shadow stack backtrace instead of the normal backtrace.
> > +
> >  * Support for .gdb_index sections with version less than 7 has been
> >    removed.
> 
> This part is okay.
> 
> > +A shadow stack is supported, for instance, with the Intel
> > +Control-Flow Enforcement Technology (@xref{CET}) on x86 and the
> > +Guarded Control Stack feature (@xref{GCS}) on AArch64.
>             ^^^^^^^^^^
> This should use @pxref, not @xref.

Yes, thanks!

> > +This option may be combined with “-frame-info” and implies ‘-no-filters’
> > +and ‘-frame-arguments none’.     ^^^^^^^^^^^^^             ^^^^^^^^^^^^^
>        ^^^^^^^^^^^^^^^^^^^^^^^
> All those should use @code and without the quotes.

Yes, will fix, too.

> > +@node GCS
> >  @subsubsection AArch64 Guarded Control Stack  @cindex Guarded
> Control
> > Stack, AArch64  @cindex GCS, AArch64 @@ -27248,6 +27274,7 @@
> registers
> >
> >  @end itemize
> >
> > +@node CET
> >  @subsubsection Intel Control-Flow Enforcement Technology.
> >  @cindex Intel Control-Flow Enforcement Technology.
> 
> You are adding @node's, but AFAICT you don't change the @menu of the
> parent section.  That can only work with recent Texinfo versions, where
> @menus are updated automatically.  Please update the parent @menu as
> part of the patch, to not rely on those recent versions.
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>

Ah, I wasn't aware, thanks!

Now it looks like 
@menu
* AArch64::
* GCS::
* x86::
* CET::
* Alpha::
[...]
@end menu

This looks somehow unconventional since CET/GCS are subsubsections
(and no subsections like AArch64/x86) but since this is a requirement
of old texinfo versions I'll simply add it.

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] 49+ messages in thread

* RE: [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2026-01-23  8:46   ` Eli Zaretskii
@ 2026-02-13 19:17     ` Schimpe, Christina
  0 siblings, 0 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-02-13 19:17 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Eli Zaretskii <eliz@gnu.org>
> Sent: Freitag, 23. Januar 2026 09:46
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command
> 
> > From: Christina Schimpe <christina.schimpe@intel.com>
> > Cc: thiago.bauermann@linaro.org
> > Date: Fri, 23 Jan 2026 08:05:31 +0000
> >
> >  gdb/NEWS                                      |  10 ++
> >  gdb/doc/gdb.texinfo                           |  47 ++++++
> >  gdb/mi/mi-cmd-stack.c                         | 142 ++++++++++++++++++
> >  gdb/mi/mi-cmds.c                              |   2 +
> >  gdb/mi/mi-cmds.h                              |   1 +
> >  gdb/shadow-stack.c                            |  18 ++-
> >  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, 362 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 37b3add11ed..d3991a495f4 100644
> > --- a/gdb/NEWS
> > +++ b/gdb/NEWS
> > @@ -177,6 +177,16 @@ qExecAndArgs
> >       deprecated since GDB 10.  Users who need to control the size of a
> >       memory port's internal buffer can use the 'setvbuf' procedure.
> >
> > +* 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.
> > +
> 
> This part is okay.
> 
> > +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
>                      ^^^^^^^^^^^^^^^^^^
> This should be in @code.
> 
> > +If invoked without arguments, this command prints a backtrace for the
> > +whole shadow stack.  Like the -stack-list-frames command, if given
> > +two
>                                  ^^^^^^^^^^^^^^^^^^ Likewise.

Yes, will fix!

> > +For frame 1 we can see that we only print @var{level}, @var{addr} and
> > +@var{arch}.
> 
> Using "we" in the manual is not the best way of expressing such stuff.
> How about
> 
>   Note that for frame 1 @value{GDBN} printed only @var{level},
>   @var{addr}, and @var{arch}.

Oups, I was too quick when writing the docs here.
I further noticed that I missed to update the docs for v2 in this patch since the output for
signals changed also for the mi command.
Based on the results of our discussion with Thiago it might change even further since it's
planned to print "<signal handler called>" just like the normal bt command:
https://sourceware.org/pipermail/gdb-patches/2026-January/224054.html.
But the discussion is not complete yet.

In any case your suggestion sounds very good to me, I'll use that one and adapt it due to
the reasons described above for v3.

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

Thank you for the review.

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] 49+ messages in thread

* Re: [PATCH v2 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c.
  2026-01-23  8:05 ` [PATCH v2 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
@ 2026-02-19 17:32   ` Tom Tromey
  2026-04-09 12:40     ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Tom Tromey @ 2026-02-19 17:32 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

>>>>> 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'.  The function will also be
> used in a following commit.

Looks reasonable to me.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

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

* Re: [PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.
  2026-01-23  8:05 ` [PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
  2026-01-23  8:47   ` Eli Zaretskii
@ 2026-02-19 17:41   ` Tom Tromey
  1 sibling, 0 replies; 49+ messages in thread
From: Tom Tromey @ 2026-02-19 17:41 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

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

> On x86 with CET or on ARM with GCS, there can be elements on the shadow
> stack which are not return addresses.  In this case, we don't want to print
> the shadow stack element, but a string instead which describes the frame
> similar to the normal backtrace command for dummy frames or signals.

> Provide a gdbarch hook to distinguish between return and non-return
> addresses and to configure a string which is printed instead of the
> shadow stack element.

This seems reasonable enough to me.

Could you double-check that everything is consistent by re-running
check-gdbarch.py on your tree?  If that is silent then it's fine.

> +Return true, if FRAME does not contain a return address in FRAME->VALUE
> +but another valid value for the architecture's shadow stack.  In this case,
> +also the string frame_type has to be configured to display the type in the
> +shadow stack backtrace.

I wonder if you considered returning a std::optional<std::string> here
instead, just to be sure that the return value and the required
auxiliary data could not possibly be out of sync.

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

Tom

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

* Re: [PATCH v2 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux.
  2026-01-23  8:05 ` [PATCH v2 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
@ 2026-02-19 17:43   ` Tom Tromey
  0 siblings, 0 replies; 49+ messages in thread
From: Tom Tromey @ 2026-02-19 17:43 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

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

> Implement the gdbarch hook is_no_return_shadow_stack_address to detect
> this scenario to print the shadow stack backtrace correctly.

I read this and didn't see any problems.
However you will have to self-approve in the end since I don't really
know anything about the arch details.

Reviewed-By: Tom Tromey <tom@tromey.com>

Tom

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

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-01-23  8:05 ` [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
@ 2026-02-19 17:55   ` Tom Tromey
  2026-02-27 18:09     ` Schimpe, Christina
  2026-03-06  3:15   ` Thiago Jung Bauermann
  2026-04-09 12:06   ` Schimpe, Christina
  2 siblings, 1 reply; 49+ messages in thread
From: Tom Tromey @ 2026-02-19 17:55 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

>>>>> 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 patch a command line option "-shadow" will be added to
> the backtrace command to print the shadow stack backtrace.  This requires
> more target-independent logic to handle the shadow stack pointer.  To
> avoid that we duplicate the logic, add new source and header files
> "shadow-stack" for the implementation of shadow_stack_push and shadow
> stack pointer unwinding in a target-independent way.

This looks reasonable to me, but I think someone else ought to examine
the arch-specific bits.

Like I mentioned in another note, be sure to run check-gdbarch.py.

I had a nit or two, plus a question, but nothing really serious.

> +static void
> +amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg *reg,
> +		const frame_info_ptr &this_frame)
> +{
> +  if (regnum == gdbarch_pc_regnum (gdbarch))
> +    reg->how = DWARF2_FRAME_REG_RA;
> +  else if (regnum == gdbarch_sp_regnum (gdbarch))
> +    reg->how = DWARF2_FRAME_REG_CFA;
> +  else if (regnum == AMD64_PL3_SSP_REGNUM)
> +    {
> +      reg->how = DWARF2_FRAME_REG_FN;
> +      reg->loc.fn = dwarf2_prev_ssp;
> +    }
> +}
> +
>  void
>  amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
>  		const target_desc *default_tdesc)
> @@ -3650,6 +3667,9 @@ amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
>    set_gdbarch_in_indirect_branch_thunk (gdbarch,
>  					amd64_in_indirect_branch_thunk);
 
> +  set_gdbarch_ssp_regnum (gdbarch, tdep->ssp_regnum);
> +  dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
> +

I was curious why this code refers to tdep->ssp_regnum but then the
regnum seems to be hard-coded in amd64_init_reg.

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

Mildly prefer a trailing "," on enums.  That way if a new value is
needed the patch doesn't have to touch this line.

> +/* See shadow-stack.h.  */
> +
> +void shadow_stack_push (regcache *regcache, const CORE_ADDR new_addr)

Newline after 'void'

Reviewed-By: Tom Tromey <tom@tromey.com>

Tom

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

* Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-01-23  8:05 ` [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
  2026-01-23  8:52   ` Eli Zaretskii
@ 2026-02-19 18:19   ` Tom Tromey
  2026-04-09 16:48     ` Schimpe, Christina
  2026-03-06  4:31   ` Thiago Jung Bauermann
  2 siblings, 1 reply; 49+ messages in thread
From: Tom Tromey @ 2026-02-19 18:19 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

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

> Add command option '-shadow" to the backtrace command to print the shadow
> stack backtrace instead of the normal backtrace.

Thanks.

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

I think it is fine to just drop all the annotation changes.
As far as I know, no client even uses annotation_level > 1.

Emacs, maybe the only existing user of annotations, passes --fullname
which uses:

	  case 'f':
	    annotation_level = 1;

> +  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");

The extra scope doesn't really look necessary here, since:

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

... it seems fine to emit this text before the tuple emitter is
destroyed?  And there's already an extras scope from the "then" block.

> +  gdb_flush (gdb_stdout);

Is flushing really needed?  I feel like I saw this in a different patch
in the series as well, and forgot to ask about it there.

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

It seems odd to combine a bool return and an out parameter when the enum
has a "no_error" value.

Maybe just a bool return would be more appropriate and then the callers
could set their own out parameters.  Especially since one caller doesn't
even need this.

> -static const char *const print_frame_info_choices[] =
> +const char *const print_frame_info_choices[] =

This is public now but I didn't see other uses of it.

Tom

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

* Re: [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command
  2026-01-23  8:05 ` [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
  2026-01-23  8:46   ` Eli Zaretskii
@ 2026-02-19 18:26   ` Tom Tromey
  1 sibling, 0 replies; 49+ messages in thread
From: Tom Tromey @ 2026-02-19 18:26 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann

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

> +void
> +mi_cmd_shadow_stack_list_frames (const char *command,
> +				 const char *const *argv,
> +				 int argc)

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

It seems like more of this could be shared with the earlier patch.
Replicating a lot of the logic doesn't seem good.

The gdb_printfs in the other code can be handled by changing them to use
ui_out::text or ui_out::message instead.

> +  if (!curr.has_value ())
> +    error (_("-shadow-stack-list-frames: Not enough frames on the shadow "
> +	     "stack."));

For things like this maybe the API has to be changed a little but that
seems fine.

Tom

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

* RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-02-19 17:55   ` Tom Tromey
@ 2026-02-27 18:09     ` Schimpe, Christina
  2026-02-27 18:26       ` Tom Tromey
  2026-02-27 22:54       ` Thiago Jung Bauermann
  0 siblings, 2 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-02-27 18:09 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Donnerstag, 19. Februar 2026 18:55
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 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 patch a command line option "-shadow" will be added to
> > the backtrace command to print the shadow stack backtrace.  This
> > requires more target-independent logic to handle the shadow stack
> > pointer.  To avoid that we duplicate the logic, add new source and
> > header files "shadow-stack" for the implementation of
> > shadow_stack_push and shadow stack pointer unwinding in a target-
> independent way.
> 
> This looks reasonable to me, but I think someone else ought to examine the
> arch-specific bits.

Ok, I'll wait for more feedback on this. Thiago has run some tests for GCS already, 
but only with v1 AFAIK.

> Like I mentioned in another note, be sure to run check-gdbarch.py.

I assume you mean gdb/gdbarch.py.

> I had a nit or two, plus a question, but nothing really serious.
> 
> > +static void
> > +amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg
> *reg,
> > +		const frame_info_ptr &this_frame)
> > +{
> > +  if (regnum == gdbarch_pc_regnum (gdbarch))
> > +    reg->how = DWARF2_FRAME_REG_RA;
> > +  else if (regnum == gdbarch_sp_regnum (gdbarch))
> > +    reg->how = DWARF2_FRAME_REG_CFA;
> > +  else if (regnum == AMD64_PL3_SSP_REGNUM)
> > +    {
> > +      reg->how = DWARF2_FRAME_REG_FN;
> > +      reg->loc.fn = dwarf2_prev_ssp;
> > +    }
> > +}
> > +
> >  void
> >  amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
> >  		const target_desc *default_tdesc)
> > @@ -3650,6 +3667,9 @@ amd64_init_abi (struct gdbarch_info info, struct
> gdbarch *gdbarch,
> >    set_gdbarch_in_indirect_branch_thunk (gdbarch,
> >  					amd64_in_indirect_branch_thunk);
> 
> > +  set_gdbarch_ssp_regnum (gdbarch, tdep->ssp_regnum);
> > + dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
> > +
> 
> I was curious why this code refers to tdep->ssp_regnum but then the regnum
> seems to be hard-coded in amd64_init_reg.

In case CET shadow stack is not available due to missing kernel or HW support 
"tdep->ssp_regnum  == -1".
It should be cleaner to check "if tdep->ssp_regnum != -1" and only then configure it
using tdep->ssp_regnum because the default is -1 anyway.

For amd64_init_reg, I could be using "gdbarch_ssp_regnum (gdbarch)" now, too.
It makes the code more consistent.

Thanks for catching that.

> > +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
> 
> Mildly prefer a trailing "," on enums.  That way if a new value is needed the
> patch doesn't have to touch this line.

I agree, will fix.

> > +/* See shadow-stack.h.  */
> > +
> > +void shadow_stack_push (regcache *regcache, const CORE_ADDR
> new_addr)
> 
> Newline after 'void'

Will fix.

> Reviewed-By: Tom Tromey <tom@tromey.com>
> 
> Tom

Thank you for the review.

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] 49+ messages in thread

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-02-27 18:09     ` Schimpe, Christina
@ 2026-02-27 18:26       ` Tom Tromey
  2026-03-02 11:53         ` Schimpe, Christina
  2026-02-27 22:54       ` Thiago Jung Bauermann
  1 sibling, 1 reply; 49+ messages in thread
From: Tom Tromey @ 2026-02-27 18:26 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Tom Tromey, gdb-patches, thiago.bauermann

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

>> Like I mentioned in another note, be sure to run check-gdbarch.py.

> I assume you mean gdb/gdbarch.py.

No, there's a check-gdbarch.py script that does some extra (but slow)
checking of gdbarch changes.  You may need to 'pip install' a couple
things to run it.  If that's a problem, it's not a huge deal, I can
always run it later.

Tom

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

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-02-27 18:09     ` Schimpe, Christina
  2026-02-27 18:26       ` Tom Tromey
@ 2026-02-27 22:54       ` Thiago Jung Bauermann
  1 sibling, 0 replies; 49+ messages in thread
From: Thiago Jung Bauermann @ 2026-02-27 22:54 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: Donnerstag, 19. Februar 2026 18:55
>> To: Schimpe, Christina <christina.schimpe@intel.com>
>> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
>> Subject: Re: [PATCH v2 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 patch a command line option "-shadow" will be added to
>> > the backtrace command to print the shadow stack backtrace.  This
>> > requires more target-independent logic to handle the shadow stack
>> > pointer.  To avoid that we duplicate the logic, add new source and
>> > header files "shadow-stack" for the implementation of
>> > shadow_stack_push and shadow stack pointer unwinding in a target-
>> independent way.
>> 
>> This looks reasonable to me, but I think someone else ought to examine the
>> arch-specific bits.
>
> Ok, I'll wait for more feedback on this. Thiago has run some tests for GCS already, 
> but only with v1 AFAIK.

I'm currently looking into these patches. Sorry for the delay. I'll
provide my feedback next week.

-- 
Thiago

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

* RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-02-27 18:26       ` Tom Tromey
@ 2026-03-02 11:53         ` Schimpe, Christina
  2026-04-09  9:49           ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-03-02 11:53 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Freitag, 27. Februar 2026 19:26
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: Tom Tromey <tom@tromey.com>; gdb-patches@sourceware.org;
> thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack
> pointer.
> 
> >>>>> Schimpe, Christina <christina.schimpe@intel.com> writes:
> 
> >> Like I mentioned in another note, be sure to run check-gdbarch.py.
> 
> > I assume you mean gdb/gdbarch.py.
> 
> No, there's a check-gdbarch.py script that does some extra (but slow) checking
> of gdbarch changes.  You may need to 'pip install' a couple things to run it.  If
> that's a problem, it's not a huge deal, I can always run it later.
> 
> Tom

Ah, I missed that. Will check it out. 

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] 49+ messages in thread

* RE: [PATCH v2 0/9] Add new command to print the shadow stack backtrace
  2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
                   ` (8 preceding siblings ...)
  2026-01-23  8:05 ` [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
@ 2026-03-02 12:39 ` Schimpe, Christina
  9 siblings, 0 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-03-02 12:39 UTC (permalink / raw)
  To: gdb-patches; +Cc: thiago.bauermann

Just some update for the open for inferior calls, please see below.

[...]

> 3) For non-return addresses on the shadow stack, we want to display a string,
> as already implemented for signals.  For inferior calls, we also want to display
> <function called by GDB>.  The return address for inferior calls is pushed onto
> the shadow stack by GDB, but we currently don’t have a way to distinguish
> this address from normal return addresses.  Thiago suggested pushing the
> return address together with a marker, but it’s still unclear how this marker
> should look like.

I investigated this a bit, and concluded that it should be possible to implement without
manipulating the shadow stack further (e.g. with marker element).

There are several options, and I'll probably go ahead and implement this by detecting
the inferior call breakpoint address in the shadow stack unwind logic.

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] 49+ messages in thread

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-01-23  8:05 ` [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
  2026-02-19 17:55   ` Tom Tromey
@ 2026-03-06  3:15   ` Thiago Jung Bauermann
  2026-03-06  3:57     ` Thiago Jung Bauermann
  2026-04-09 12:06   ` Schimpe, Christina
  2 siblings, 1 reply; 49+ messages in thread
From: Thiago Jung Bauermann @ 2026-03-06  3:15 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches


Hello Christina,

Thank you for addressing my review comments! Just a couple of comments:

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

> +static bool
> +amd64_linux_top_addr_empty_shadow_stack
> +  (gdbarch *gdbarch, const CORE_ADDR addr,
> +   const std::pair<CORE_ADDR, CORE_ADDR> range)
>  {
> -  if (regnum == gdbarch_pc_regnum (gdbarch))
> -    reg->how = DWARF2_FRAME_REG_RA;
> -  else if (regnum == gdbarch_sp_regnum (gdbarch))
> -    reg->how = DWARF2_FRAME_REG_CFA;
> -  else if (regnum == AMD64_PL3_SSP_REGNUM)
> -    {
> -      reg->how = DWARF2_FRAME_REG_FN;
> -      reg->loc.fn = amd64_linux_dwarf2_prev_ssp;
> -    }
> +  return addr == range.second;
>  }

Next week I'll provide the AArch64 implementation of this hook,
if you can squash it into your patch.

> diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
> new file mode 100644
> index 00000000000..77d7c07d970
> --- /dev/null
> +++ b/gdb/shadow-stack.c
> @@ -0,0 +1,166 @@
> +/* Manage a shadow stack pointer for GDB, the GNU debugger.
> +
> +   Copyright (C) 2024-2026 Free Software Foundation, Inc.
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include "defs.h"
> +#include "arch-utils.h"
> +#include "gdbcore.h"
> +#include "extract-store-integer.h"
> +#include "frame.h"
> +#include "frame-unwind.h"
> +#include "shadow-stack.h"
> +
> +class regcache;

This forward declaration should be in shadow-stack.h, since the
shadow_stack_push prototype declared there uses it.

-- 
Thiago

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

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-03-06  3:15   ` Thiago Jung Bauermann
@ 2026-03-06  3:57     ` Thiago Jung Bauermann
  2026-04-09 11:57       ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Thiago Jung Bauermann @ 2026-03-06  3:57 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches

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

> Christina Schimpe <christina.schimpe@intel.com> writes:
>
>> +static bool
>> +amd64_linux_top_addr_empty_shadow_stack
>> +  (gdbarch *gdbarch, const CORE_ADDR addr,
>> +   const std::pair<CORE_ADDR, CORE_ADDR> range)
>>  {
>> -  if (regnum == gdbarch_pc_regnum (gdbarch))
>> -    reg->how = DWARF2_FRAME_REG_RA;
>> -  else if (regnum == gdbarch_sp_regnum (gdbarch))
>> -    reg->how = DWARF2_FRAME_REG_CFA;
>> -  else if (regnum == AMD64_PL3_SSP_REGNUM)
>> -    {
>> -      reg->how = DWARF2_FRAME_REG_FN;
>> -      reg->loc.fn = amd64_linux_dwarf2_prev_ssp;
>> -    }
>> +  return addr == range.second;
>>  }
>
> Next week I'll provide the AArch64 implementation of this hook,
> if you can squash it into your patch.

It turns out I had it in my notes for patch 6:

diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 836af863d3ad..112d42c6a1ac 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -1909,6 +1909,23 @@ aarch64_pop_gcs_entry (regcache *regs)
   regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr + 8);
 }
 
+static bool
+aarch64_top_addr_empty_shadow_stack (gdbarch *gdbarch, const CORE_ADDR addr,
+				     const std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+  gdb_assert (addr >= range.first);
+
+  /* For AArch64, addr must be strictly less than the upper address in the
+     range, but other architectures allow it to be equal to the upper
+     address when the stack is empty so GDB core works with those addresses
+     and can send them our way.  */
+  gdb_assert (addr <= range.second);
+
+  /* The GCS grows down, and the oldest entry isn't an address.
+     Just the value '0'.  */
+  return addr >= range.second - 8;
+}
+
 /* Implement the "push_dummy_call" gdbarch method.  */
 
 static CORE_ADDR
@@ -4790,7 +4807,12 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
 
   /* AArch64's shadow stack pointer is the GCSPR.  */
   if (tdep->has_gcs ())
-    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
+    {
+      /* AArch64's shadow stack pointer is the GCSPR.  */
+      set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
+      set_gdbarch_top_addr_empty_shadow_stack
+	(gdbarch, aarch64_top_addr_empty_shadow_stack);
+    }
 
   /* ABI */
   set_gdbarch_short_bit (gdbarch, 16);


So with the above, for this patch:

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>

-- 
Thiago

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

* Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-01-23  8:05 ` [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
  2026-01-23  8:52   ` Eli Zaretskii
  2026-02-19 18:19   ` Tom Tromey
@ 2026-03-06  4:31   ` Thiago Jung Bauermann
  2026-03-06  9:39     ` Schimpe, Christina
  2026-04-09 15:12     ` Schimpe, Christina
  2 siblings, 2 replies; 49+ messages in thread
From: Thiago Jung Bauermann @ 2026-03-06  4:31 UTC (permalink / raw)
  To: Christina Schimpe; +Cc: gdb-patches


I'll have a closer look at this patch next week, including your comments
about the count argument to the
get_trailing_outermost_shadow_stack_frame_info in the thread for v1 of
this series.. Unfortunately I didn't have much time to dig into it yet.

Some minor comments for now:

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

> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> index 656daa0f0ee..a4eabccf667 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -1963,6 +1963,29 @@ amd64_linux_top_addr_empty_shadow_stack
>    return addr == range.second;
>  }
>  
> +/* Return the number of elements which are currently on the shadow stack
> +   based on the shadow stack memory RANGE [start_address, end_address)
> +   of the current thread.  In case shadow stack is not enabled for the
> +   current thread, return -1.  */
> +
> +static long
> +amd64_linux_get_shadow_stack_size
> +  (gdbarch *gdbarch,
> +   const std::optional<CORE_ADDR> ssp,
> +   const std::pair<CORE_ADDR, CORE_ADDR> range)
> +{
> +  /* For x86, if we don't have a shadow stack pointer, we can assume
> +     that the shadow stack is disabled for the current thread.  */
> +  if (!ssp.has_value ())
> +    return -1;

As I mention a bit below, the only caller of this function passes a
value for the ssp argument, so this is dead code.

> +  const unsigned long shadow_stack_bytes = range.second - *ssp;
> +
> +  gdb_assert ((shadow_stack_bytes % 8) == 0);

I don't think this should be an assert. If it fails, it triggers an
internal error in GDB.  In this case it could indeed mean an internal
error (GDB somehow got the SSP or range wrong), but it could also be
(and probably more likely) an inconsistent state of the inferior. This
can happen in a program being debugged so GDB should be able to handle
it gracefully, and if possible provide useful information to the user.

> +  return shadow_stack_bytes / 8;
> +}
> +/* 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)

The reason argument can also be a reference.

> +{
> +  /* 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 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.  */
> +
> +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)
> +{
> +  gdb_assert (gdbarch_get_shadow_stack_size_p (gdbarch));
> +
> +  const long shadow_stack_size
> +    = gdbarch_get_shadow_stack_size (gdbarch,
> +				     std::optional<CORE_ADDR> (frame.ssp),
> +				     range);

This is the only caller of gdbarch_get_shadow_stack_size. Does its ssp
argument need to be std::optional<CORE_ADDR>, or can it simply be a
CORE_ADDR?

> +  /* We should only get here in case shadow stack is enabled for the
> +     current thread.  */
> +  gdb_assert (shadow_stack_size >= 0);
> +
> +  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, level, ssp_update_direction::outer);
> +
> +  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, (unsigned long) level,
> +      ssp_unwind_stop_reason::no_error});
> +}
> diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
> index 5f8395ec047..5370becfc9a 100644
> --- a/gdb/shadow-stack.h
> +++ b/gdb/shadow-stack.h
> @@ -35,4 +35,10 @@ void shadow_stack_push (regcache *regcache, const CORE_ADDR new_addr);
>  value *dwarf2_prev_ssp (const frame_info_ptr &this_frame,
>  			void **this_cache, int regnum);
>  
> +/* Implementation of "backtrace shadow" comand.  */
> +
> +void backtrace_shadow_command
> +  (const frame_print_options &fp_opts,
> +   const char *count_exp, int from_tty);
> +
>  #endif /* GDB_SHADOW_STACK_H */

This header needs to forward-declare "struct frame_print_options;",
similarly to the "class regcache;" forward declaration.

Actually, one thing I missed in my review of patch 1 is that it also
needs to forward-declare "class frame_info_ptr;" which is used in the
prototype of dwarf2_prev_ssp.

-- 
Thiago

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

* RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-03-06  4:31   ` Thiago Jung Bauermann
@ 2026-03-06  9:39     ` Schimpe, Christina
  2026-04-09 15:12     ` Schimpe, Christina
  1 sibling, 0 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-03-06  9:39 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Freitag, 6. März 2026 05:31
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print
> the shadow stack backtrace.
> 
> 
> I'll have a closer look at this patch next week, including your comments about
> the count argument to the get_trailing_outermost_shadow_stack_frame_info
> in the thread for v1 of this series.. Unfortunately I didn't have much time to
> dig into it yet.
> 
> Some minor comments for now:
> 
> Christina Schimpe <christina.schimpe@intel.com> writes:
> 
> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> > 656daa0f0ee..a4eabccf667 100644
> > --- a/gdb/amd64-linux-tdep.c
> > +++ b/gdb/amd64-linux-tdep.c
> > @@ -1963,6 +1963,29 @@ amd64_linux_top_addr_empty_shadow_stack
> >    return addr == range.second;
> >  }
> >
> > +/* Return the number of elements which are currently on the shadow
> stack
> > +   based on the shadow stack memory RANGE [start_address, end_address)
> > +   of the current thread.  In case shadow stack is not enabled for the
> > +   current thread, return -1.  */
> > +
> > +static long
> > +amd64_linux_get_shadow_stack_size
> > +  (gdbarch *gdbarch,
> > +   const std::optional<CORE_ADDR> ssp,
> > +   const std::pair<CORE_ADDR, CORE_ADDR> range) {
> > +  /* For x86, if we don't have a shadow stack pointer, we can assume
> > +     that the shadow stack is disabled for the current thread.  */
> > +  if (!ssp.has_value ())
> > +    return -1;
> 
> As I mention a bit below, the only caller of this function passes a value for the
> ssp argument, so this is dead code.
> 
> > +  const unsigned long shadow_stack_bytes = range.second - *ssp;
> > +
> > +  gdb_assert ((shadow_stack_bytes % 8) == 0);
> 
> I don't think this should be an assert. If it fails, it triggers an internal error in
> GDB.  In this case it could indeed mean an internal error (GDB somehow got
> the SSP or range wrong), but it could also be (and probably more likely) an
> inconsistent state of the inferior. This can happen in a program being
> debugged so GDB should be able to handle it gracefully, and if possible
> provide useful information to the user.
> 
> > +  return shadow_stack_bytes / 8;
> > +}
> 
>   ⋮
> 
> > +/* 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)
> 
> The reason argument can also be a reference.
> 
> > +{
> > +  /* 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 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.  */
> > +
> > +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) {
> > +  gdb_assert (gdbarch_get_shadow_stack_size_p (gdbarch));
> > +
> > +  const long shadow_stack_size
> > +    = gdbarch_get_shadow_stack_size (gdbarch,
> > +				     std::optional<CORE_ADDR> (frame.ssp),
> > +				     range);
> 
> This is the only caller of gdbarch_get_shadow_stack_size. Does its ssp
> argument need to be std::optional<CORE_ADDR>, or can it simply be a
> CORE_ADDR?
> 
> > +  /* We should only get here in case shadow stack is enabled for the
> > +     current thread.  */
> > +  gdb_assert (shadow_stack_size >= 0);
> > +
> > +  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, level, ssp_update_direction::outer);
> > +
> > +  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, (unsigned long) level,
> > +      ssp_unwind_stop_reason::no_error});
> > +}
> 
>   ⋮
> 
> > diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h index
> > 5f8395ec047..5370becfc9a 100644
> > --- a/gdb/shadow-stack.h
> > +++ b/gdb/shadow-stack.h
> > @@ -35,4 +35,10 @@ void shadow_stack_push (regcache *regcache, const
> > CORE_ADDR new_addr);  value *dwarf2_prev_ssp (const frame_info_ptr
> &this_frame,
> >  			void **this_cache, int regnum);
> >
> > +/* Implementation of "backtrace shadow" comand.  */
> > +
> > +void backtrace_shadow_command
> > +  (const frame_print_options &fp_opts,
> > +   const char *count_exp, int from_tty);
> > +
> >  #endif /* GDB_SHADOW_STACK_H */
> 
> This header needs to forward-declare "struct frame_print_options;", similarly
> to the "class regcache;" forward declaration.
> 
> Actually, one thing I missed in my review of patch 1 is that it also needs to
> forward-declare "class frame_info_ptr;" which is used in the prototype of
> dwarf2_prev_ssp.
> 
> --
> Thiago

Thank you for the feedback.

Together with the -past-main functionality, I'll also add some more changes to this patch.
I just realized recently that a shadow stack frame specific gdbarch is probably a good idea,
since we use whatever is current at the point of running this command.
I am not sure if it ever changes for IA, but it might be a good idea to add it to be safe.

But I'm still not sure about some opens for this series, which I have summarized in the cover letter.
https://sourceware.org/pipermail/gdb-patches/2026-January/224388.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] 49+ messages in thread

* RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-03-02 11:53         ` Schimpe, Christina
@ 2026-04-09  9:49           ` Schimpe, Christina
  2026-04-14 17:34             ` Tom Tromey
  0 siblings, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-09  9:49 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Schimpe, Christina
> Sent: Montag, 2. März 2026 12:53
> To: Tom Tromey <tom@tromey.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack
> pointer.
> 
> > -----Original Message-----
> > From: Tom Tromey <tom@tromey.com>
> > Sent: Freitag, 27. Februar 2026 19:26
> > To: Schimpe, Christina <christina.schimpe@intel.com>
> > Cc: Tom Tromey <tom@tromey.com>; gdb-patches@sourceware.org;
> > thiago.bauermann@linaro.org
> > Subject: Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow
> > stack pointer.
> >
> > >>>>> Schimpe, Christina <christina.schimpe@intel.com> writes:
> >
> > >> Like I mentioned in another note, be sure to run check-gdbarch.py.
> >
> > > I assume you mean gdb/gdbarch.py.
> >
> > No, there's a check-gdbarch.py script that does some extra (but slow)
> > checking of gdbarch changes.  You may need to 'pip install' a couple
> > things to run it.  If that's a problem, it's not a huge deal, I can always run it
> later.
> >
> > Tom
> 
> Ah, I missed that. Will check it out.
> 
> Thanks,
> Christina

Hi Tom,

Sorry for getting back delayed response. I am seeing the following when running check-gdbarch.py:

~~~
$ ./check-gdbarch.py
never set: shadow_stack_element_size_aligned
~~~

Given that I assign a pre-default I don't have to configure it for amd64 specifically.
But starting with this patch, this message will always appear when running the script. 

Should we change the script for this case?

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

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

* RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-03-06  3:57     ` Thiago Jung Bauermann
@ 2026-04-09 11:57       ` Schimpe, Christina
  2026-04-10  5:03         ` Thiago Jung Bauermann
  0 siblings, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-09 11:57 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

Hi Thiago, 

Thank you for sharing the patch and the review.

I wonder if we should we split this commit into 2 separate ones:
#1 "aarch64: Implement gdbarch function top_addr_empty_shadow_stack."
#2 "gdb: Generalize handling of the shadow stack pointer."

This way you could be the author of patch #2, and I'll add my self as co-author
since I'll also move my changes from aarch64-linux-tdep.c into this patch.

With the commit "gdb: Generalize handling.." only, the GCS unwinding logic is
still functional and will be generalized in the aarch64 specific patch.
 
 I'll also include the patch in my v3 then.

What do you think?

Christina 

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Freitag, 6. März 2026 04:57
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack
> pointer.
> 
> Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:
> 
> > Christina Schimpe <christina.schimpe@intel.com> writes:
> >
> >> +static bool
> >> +amd64_linux_top_addr_empty_shadow_stack
> >> +  (gdbarch *gdbarch, const CORE_ADDR addr,
> >> +   const std::pair<CORE_ADDR, CORE_ADDR> range)
> >>  {
> >> -  if (regnum == gdbarch_pc_regnum (gdbarch))
> >> -    reg->how = DWARF2_FRAME_REG_RA;
> >> -  else if (regnum == gdbarch_sp_regnum (gdbarch))
> >> -    reg->how = DWARF2_FRAME_REG_CFA;
> >> -  else if (regnum == AMD64_PL3_SSP_REGNUM)
> >> -    {
> >> -      reg->how = DWARF2_FRAME_REG_FN;
> >> -      reg->loc.fn = amd64_linux_dwarf2_prev_ssp;
> >> -    }
> >> +  return addr == range.second;
> >>  }
> >
> > Next week I'll provide the AArch64 implementation of this hook, if you
> > can squash it into your patch.
> 
> It turns out I had it in my notes for patch 6:
> 
> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> 836af863d3ad..112d42c6a1ac 100644
> --- a/gdb/aarch64-tdep.c
> +++ b/gdb/aarch64-tdep.c
> @@ -1909,6 +1909,23 @@ aarch64_pop_gcs_entry (regcache *regs)
>    regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr + 8);
> }
> 
> +static bool
> +aarch64_top_addr_empty_shadow_stack (gdbarch *gdbarch, const
> CORE_ADDR addr,
> +				     const std::pair<CORE_ADDR, CORE_ADDR>
> range) {
> +  gdb_assert (addr >= range.first);
> +
> +  /* For AArch64, addr must be strictly less than the upper address in the
> +     range, but other architectures allow it to be equal to the upper
> +     address when the stack is empty so GDB core works with those addresses
> +     and can send them our way.  */
> +  gdb_assert (addr <= range.second);
> +
> +  /* The GCS grows down, and the oldest entry isn't an address.
> +     Just the value '0'.  */
> +  return addr >= range.second - 8;
> +}
> +
>  /* Implement the "push_dummy_call" gdbarch method.  */
> 
>  static CORE_ADDR
> @@ -4790,7 +4807,12 @@ aarch64_gdbarch_init (struct gdbarch_info info,
> struct gdbarch_list *arches)
> 
>    /* AArch64's shadow stack pointer is the GCSPR.  */
>    if (tdep->has_gcs ())
> -    set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
> +    {
> +      /* AArch64's shadow stack pointer is the GCSPR.  */
> +      set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
> +      set_gdbarch_top_addr_empty_shadow_stack
> +	(gdbarch, aarch64_top_addr_empty_shadow_stack);
> +    }
> 
>    /* ABI */
>    set_gdbarch_short_bit (gdbarch, 16);
> 
> 
> So with the above, for this patch:
> 
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> 
> --
> Thiago
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

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

* RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-01-23  8:05 ` [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
  2026-02-19 17:55   ` Tom Tromey
  2026-03-06  3:15   ` Thiago Jung Bauermann
@ 2026-04-09 12:06   ` Schimpe, Christina
  2026-04-10  5:05     ` Thiago Jung Bauermann
  2 siblings, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-09 12:06 UTC (permalink / raw)
  To: Schimpe, Christina, gdb-patches; +Cc: thiago.bauermann

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

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

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

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

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

* RE: [PATCH v2 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c.
  2026-02-19 17:32   ` Tom Tromey
@ 2026-04-09 12:40     ` Schimpe, Christina
  0 siblings, 0 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-09 12:40 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Donnerstag, 19. Februar 2026 18:33
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 4/9] gdb: Refactor 'find_symbol_funname' and
> 'info_frame_command_core' in stack.c.
> 
> >>>>> 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'.  The function will also be used in a following
> commit.
> 
> Looks reasonable to me.
> Approved-By: Tom Tromey <tom@tromey.com>
> 
> Tom

Thank you the review und approval, and sorry again for my late response.

I hope I can bring this series to a v3 state soon, there were too many
interrupts in the last couple of weeks but I try to keep myself focused now. 😊

Kind Regards,
Christina

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

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

* RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-03-06  4:31   ` Thiago Jung Bauermann
  2026-03-06  9:39     ` Schimpe, Christina
@ 2026-04-09 15:12     ` Schimpe, Christina
  2026-04-10  6:21       ` Thiago Jung Bauermann
  1 sibling, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-09 15:12 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

Hi Thiago, 

Thanks a lot for your feedback. 

For the hook gdbarch_get_shadow_stack_size I still need your GCS implementation. 😊 
I suggest a separate patch for this with you as the only author and I'll include this in my v3
then, too. Does that make sense?

Please see my comments to your feedback below. 

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Freitag, 6. März 2026 05:31
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the
> shadow stack backtrace.
> 
> 
> I'll have a closer look at this patch next week, including your comments about
> the count argument to the get_trailing_outermost_shadow_stack_frame_info
> in the thread for v1 of this series.. Unfortunately I didn't have much time to dig
> into it yet.
> 
> Some minor comments for now:
> 
> Christina Schimpe <christina.schimpe@intel.com> writes:
> 
> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> > 656daa0f0ee..a4eabccf667 100644
> > --- a/gdb/amd64-linux-tdep.c
> > +++ b/gdb/amd64-linux-tdep.c
> > @@ -1963,6 +1963,29 @@ amd64_linux_top_addr_empty_shadow_stack
> >    return addr == range.second;
> >  }
> >
> > +/* Return the number of elements which are currently on the shadow stack
> > +   based on the shadow stack memory RANGE [start_address, end_address)
> > +   of the current thread.  In case shadow stack is not enabled for the
> > +   current thread, return -1.  */
> > +
> > +static long
> > +amd64_linux_get_shadow_stack_size
> > +  (gdbarch *gdbarch,
> > +   const std::optional<CORE_ADDR> ssp,
> > +   const std::pair<CORE_ADDR, CORE_ADDR> range) {
> > +  /* For x86, if we don't have a shadow stack pointer, we can assume
> > +     that the shadow stack is disabled for the current thread.  */
> > +  if (!ssp.has_value ())
> > +    return -1;
> 
> As I mention a bit below, the only caller of this function passes a value for the
> ssp argument, so this is dead code.

I agree, thanks. I removed this.
 
> > +  const unsigned long shadow_stack_bytes = range.second - *ssp;
> > +
> > +  gdb_assert ((shadow_stack_bytes % 8) == 0);
> 
> I don't think this should be an assert. If it fails, it triggers an internal error in
> GDB.  In this case it could indeed mean an internal error (GDB somehow got
> the SSP or range wrong), but it could also be (and probably more likely) an
> inconsistent state of the inferior. This can happen in a program being debugged
> so GDB should be able to handle it gracefully, and if possible provide useful
> information to the user.

I agree. This is rather something that is outside GDB's control.

From the documentation for internal errors:
"Internal errors indicate programming errors such as assertion failures, as opposed to
   more general errors beyond the application's control.  "

So based on that I rather would choose a normal error, not an internal error.
What do you think ?

> > +  return shadow_stack_bytes / 8;
> > +}
> 
>   ⋮
> 
> > +/* 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)
> 
> The reason argument can also be a reference.

True, however, due to Tom's feedback this parameter no longer exists for this function.

> > +{
> > +  /* 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 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.  */
> > +
> > +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) {
> > +  gdb_assert (gdbarch_get_shadow_stack_size_p (gdbarch));
> > +
> > +  const long shadow_stack_size
> > +    = gdbarch_get_shadow_stack_size (gdbarch,
> > +				     std::optional<CORE_ADDR> (frame.ssp),
> > +				     range);
> 
> This is the only caller of gdbarch_get_shadow_stack_size. Does its ssp
> argument need to be std::optional<CORE_ADDR>, or can it simply be a
> CORE_ADDR?

No, I agree. I'll make it a const CORE_ADDR.
 
> > +  /* We should only get here in case shadow stack is enabled for the
> > +     current thread.  */
> > +  gdb_assert (shadow_stack_size >= 0);
> > +
> > +  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, level, ssp_update_direction::outer);
> > +
> > +  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, (unsigned long) level,
> > +      ssp_unwind_stop_reason::no_error});
> > +}
> 
>   ⋮
> 
> > diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h index
> > 5f8395ec047..5370becfc9a 100644
> > --- a/gdb/shadow-stack.h
> > +++ b/gdb/shadow-stack.h
> > @@ -35,4 +35,10 @@ void shadow_stack_push (regcache *regcache, const
> > CORE_ADDR new_addr);  value *dwarf2_prev_ssp (const frame_info_ptr
> &this_frame,
> >  			void **this_cache, int regnum);
> >
> > +/* Implementation of "backtrace shadow" comand.  */
> > +
> > +void backtrace_shadow_command
> > +  (const frame_print_options &fp_opts,
> > +   const char *count_exp, int from_tty);
> > +
> >  #endif /* GDB_SHADOW_STACK_H */
> 
> This header needs to forward-declare "struct frame_print_options;", similarly
> to the "class regcache;" forward declaration.
> 
> Actually, one thing I missed in my review of patch 1 is that it also needs to
> forward-declare "class frame_info_ptr;" which is used in the prototype of
> dwarf2_prev_ssp.

Agree - will fix.

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

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

* RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-02-19 18:19   ` Tom Tromey
@ 2026-04-09 16:48     ` Schimpe, Christina
  0 siblings, 0 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-09 16:48 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, thiago.bauermann

Hi Tom, 

Thanks a lot for the review.

> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Donnerstag, 19. Februar 2026 19:20
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print
> the shadow stack backtrace.
> 
> >>>>> Christina Schimpe <christina.schimpe@intel.com> writes:
> 
> > Add command option '-shadow" to the backtrace command to print the
> > shadow stack backtrace instead of the normal backtrace.
> 
> Thanks.
> 
> > -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"));
> > +    }
> 
> I think it is fine to just drop all the annotation changes.
> As far as I know, no client even uses annotation_level > 1.
> 
> Emacs, maybe the only existing user of annotations, passes --fullname which
> uses:
> 
> 	  case 'f':
> 	    annotation_level = 1;
> 

Ah, thanks for sharing that, I will remove them now.

> > +  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");
> 
> The extra scope doesn't really look necessary here, since:
> > +      } /* Extra scope to print frame tuple.  */
> > +
> > +      uiout->text ("\n");
> 
> ... it seems fine to emit this text before the tuple emitter is destroyed?  And
> there's already an extras scope from the "then" block.

Hm, I followed the logic in the equivalent function for the normal backtrace: stack.c:print_frame.
But right now, I cannot think about a specific reason why the extra scope is important.

In similar code in the following patch
"gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements."
I also don't have an extra scope before the new line... and it did not cause any problems.
So you're right, I can probably remove it.

> > +  gdb_flush (gdb_stdout);

> Is flushing really needed?  I feel like I saw this in a different patch in the series
> as well, and forgot to ask about it there.

In the similar function to print the normal backtrace (stack.c: do_print_frame_info), we have
the flushing, too. 

This should make sure that the output is directly visible to the user, which might be
important in case of long stack traces. 
Or in case of an error at least all frames that were correctly unwound are printed.
 
> > +
> > +/* 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.  */
> 
> It seems odd to combine a bool return and an out parameter when the enum
> has a "no_error" value.
> unwind_prev_shadow_stack_frame_info
> Maybe just a bool return would be more appropriate and then the callers
> could set their own out parameters.  Especially since one caller doesn't even
> need this.

You're right. I will change that. 

> > -static const char *const print_frame_info_choices[] =
> > +const char *const print_frame_info_choices[] =

> This is public now but I didn't see other uses of it.

It's a left-over from v1 of this series, thanks for catching it.

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

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

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-04-09 11:57       ` Schimpe, Christina
@ 2026-04-10  5:03         ` Thiago Jung Bauermann
  2026-04-10  7:53           ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Thiago Jung Bauermann @ 2026-04-10  5:03 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

Hello Christina,

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

> Thank you for sharing the patch and the review.
>
> I wonder if we should we split this commit into 2 separate ones:
> #1 "aarch64: Implement gdbarch function top_addr_empty_shadow_stack."
> #2 "gdb: Generalize handling of the shadow stack pointer."
>
> This way you could be the author of patch #2, and I'll add my self as co-author
> since I'll also move my changes from aarch64-linux-tdep.c into this patch.
>
> With the commit "gdb: Generalize handling.." only, the GCS unwinding logic is
> still functional and will be generalized in the aarch64 specific patch.
>  
>  I'll also include the patch in my v3 then.
>
> What do you think?

I don't mind the patch the way it is now, but if you think splitting is
better, that sounds good to me too.

As you mention, the main thing is to keep existing functionality working
between each patch to help with bisection.

-- 
Thiago

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

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-04-09 12:06   ` Schimpe, Christina
@ 2026-04-10  5:05     ` Thiago Jung Bauermann
  0 siblings, 0 replies; 49+ messages in thread
From: Thiago Jung Bauermann @ 2026-04-10  5:05 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

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

> I further noticed that for the gdbarch method  top_addr_empty_shadow_stack the
> gdbarch parameter is unused for the aarch64 and the amd64 implementations, so we
> could make it a gdbarch function.
>
> I'll change that for the v3 if I don't hear back otherwise.

Well spotted. I think it's a good idea.

-- 
Thiago

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

* Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-04-09 15:12     ` Schimpe, Christina
@ 2026-04-10  6:21       ` Thiago Jung Bauermann
  2026-04-10 12:12         ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Thiago Jung Bauermann @ 2026-04-10  6:21 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: gdb-patches

Hello Christina,

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

> Thanks a lot for your feedback. 

You're welcome! Thank you for moving this forward. Sorry for the big
delays on my side. More things going on at once than I'd like.

> For the hook gdbarch_get_shadow_stack_size I still need your GCS implementation. 😊 
> I suggest a separate patch for this with you as the only author and I'll include this in my v3
> then, too. Does that make sense?

I think you also need aarch64_linux_is_no_return_shadow_stack_address,
right?  I'm including both in a a patch at the end of this email.

There are FIXMEs in them to remind myself to do something better than
assert if ssp is an invalid address. Perhaps throw an error as you
mention below.

Also, feel free to not provide the aarch64 versions of the hooks in this
patch and the following one. I was planning to send them after your
series goes in.

The reason I wanted an aarch64 hook in the first patch was so that
existing GCS functionality doesn't regress, but the hooks in the other
patches are for new functionality so it's fine to have only the amd64
implementation.

>> > +  const unsigned long shadow_stack_bytes = range.second - *ssp;
>> > +
>> > +  gdb_assert ((shadow_stack_bytes % 8) == 0);
>> 
>> I don't think this should be an assert. If it fails, it triggers an internal error in
>> GDB.  In this case it could indeed mean an internal error (GDB somehow got
>> the SSP or range wrong), but it could also be (and probably more likely) an
>> inconsistent state of the inferior. This can happen in a program being debugged
>> so GDB should be able to handle it gracefully, and if possible provide useful
>> information to the user.
>
> I agree. This is rather something that is outside GDB's control.
>
> From the documentation for internal errors:
> "Internal errors indicate programming errors such as assertion failures, as opposed to
>    more general errors beyond the application's control.  "
>
> So based on that I rather would choose a normal error, not an internal error.
> What do you think ?

The effect of the error being thrown would be just that the "bt -shadow"
command is interrupted, right? If so, I think it's a good idea.

>> > +  return shadow_stack_bytes / 8;
>> > +}

-- 
Thiago

From 89f07938071f78c479ac045296afd562ab9ef93a Mon Sep 17 00:00:00 2001
From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
Date: Tue, 10 Mar 2026 21:44:35 -0300
Subject: [PATCH] WIP GDB: aarch64-linux: Add gdbarch hooks for printing shadow
 stack

Enables bt -shadow.

There are still some FIXMEs to address.
---
 gdb/aarch64-linux-tdep.c | 38 ++++++++++++++++++++++++++++++++++++++
 gdb/aarch64-tdep.c       | 20 ++++++++++++++++++++
 gdb/arch/aarch64.h       |  3 +++
 3 files changed, 61 insertions(+)

diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index f37b28067b8a..e9c9b8480aac 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -2615,6 +2615,42 @@ aarch64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
   return gcspr;
 }
 
+/* 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: a signal handling specific GCS cap token will be
+   written to the GCS.  In case this is true, configure the string which
+   describes the frame and is displayed instead of the address in the
+   shadow stack backtrace.  */
+
+static bool
+aarch64_linux_is_no_return_shadow_stack_address
+  (gdbarch *gdbarch,
+   const shadow_stack_frame_info &frame,
+   std::string &frame_type)
+{
+  /* FRAME must be a valid shadow stack frame.  */
+  bool valid_addr
+    = gdbarch_address_in_shadow_stack_memory_range (gdbarch, frame.ssp,
+						    nullptr);
+  /* FIXME: Shouldn't be an assert.  */
+  gdb_assert (valid_addr == true);
+
+  /* If the GCS entry isn't a cap token, then it should be a return
+     address.  */
+  if ((frame.value & AARCH64_GCS_CAP_ADDR_MASK) != frame.ssp)
+    return false;
+
+  /* When delivering a signal, the Linux kernel writes a cap token with the
+     token type (bits 0..11) all clear.  */
+  if ((frame.value & AARCH64_GCS_CAP_TOKEN_MASK) == 0)
+    {
+      frame_type = _("<sigframe token>");
+      return true;
+    }
+
+  return false;
+}
+
 /* AArch64 Linux implementation of the report_signal_info gdbarch
    hook.  Displays information about possible memory tag violations.  */
 
@@ -3193,6 +3229,8 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
     {
       set_gdbarch_get_shadow_stack_pointer (gdbarch,
 					aarch64_linux_get_shadow_stack_pointer);
+      set_gdbarch_is_no_return_shadow_stack_address (gdbarch,
+			      aarch64_linux_is_no_return_shadow_stack_address);
       tdep->fn_prev_gcspr = dwarf2_prev_ssp;
     }
 }
diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 112d42c6a1ac..6bf63bcc1f97 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -1926,6 +1926,24 @@ aarch64_top_addr_empty_shadow_stack (gdbarch *gdbarch, const CORE_ADDR addr,
   return addr >= range.second - 8;
 }
 
+/* Return the number of elements which are currently on the shadow stack
+   based on the shadow stack memory RANGE [start_address, end_address)
+   of the current thread.  In case shadow stack is not enabled for the
+   current thread, return -1.  */
+
+static long
+aarch64_get_shadow_stack_size (gdbarch *gdbarch, const CORE_ADDR ssp,
+			       const std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+  const unsigned long shadow_stack_bytes = range.second - ssp;
+
+  /* FIXME: Shouldn't be an assert.  */
+  gdb_assert ((shadow_stack_bytes % 8) == 0);
+
+  /* The oldest entry in the GCS isn't an address, just the value '0'. */
+  return shadow_stack_bytes / 8 - 1;
+}
+
 /* Implement the "push_dummy_call" gdbarch method.  */
 
 static CORE_ADDR
@@ -4812,6 +4830,8 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
       set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
       set_gdbarch_top_addr_empty_shadow_stack
 	(gdbarch, aarch64_top_addr_empty_shadow_stack);
+      set_gdbarch_get_shadow_stack_size (gdbarch,
+					 aarch64_get_shadow_stack_size);
     }
 
   /* ABI */
diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h
index 0e9715a4268a..7c69bfd01feb 100644
--- a/gdb/arch/aarch64.h
+++ b/gdb/arch/aarch64.h
@@ -246,6 +246,9 @@ enum aarch64_regnum
 /* Size of the SME2 ZT0 register in bytes.  */
 #define AARCH64_SME2_ZT0_SIZE 64
 
+#define AARCH64_GCS_CAP_TOKEN_MASK ((uint64_t) 0xFFF)
+#define AARCH64_GCS_CAP_ADDR_MASK ~AARCH64_GCS_CAP_TOKEN_MASK
+
 /* Feature check for Floating Point Mode Register.  */
 #ifndef HWCAP2_FPMR
 #define HWCAP2_FPMR (1ULL << 48)

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

* RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-04-10  5:03         ` Thiago Jung Bauermann
@ 2026-04-10  7:53           ` Schimpe, Christina
  0 siblings, 0 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-10  7:53 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Freitag, 10. April 2026 07:03
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack
> pointer.
> 
> Hello Christina,
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> > Thank you for sharing the patch and the review.
> >
> > I wonder if we should we split this commit into 2 separate ones:
> > #1 "aarch64: Implement gdbarch function
> top_addr_empty_shadow_stack."
> > #2 "gdb: Generalize handling of the shadow stack pointer."
> >
> > This way you could be the author of patch #2, and I'll add my self as
> > co-author since I'll also move my changes from aarch64-linux-tdep.c into
> this patch.
> >
> > With the commit "gdb: Generalize handling.." only, the GCS unwinding
> > logic is still functional and will be generalized in the aarch64 specific patch.
> >
> >  I'll also include the patch in my v3 then.
> >
> > What do you think?
> 
> I don't mind the patch the way it is now, but if you think splitting is better,
> that sounds good to me too.
> 
> As you mention, the main thing is to keep existing functionality working
> between each patch to help with bisection.
> 
> --
> Thiago

Thank you for the quick feedback. I'll split it then. 😊 

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

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

* RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-04-10  6:21       ` Thiago Jung Bauermann
@ 2026-04-10 12:12         ` Schimpe, Christina
  0 siblings, 0 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-10 12:12 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

Hi Thiago,

> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Freitag, 10. April 2026 08:21
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print
> the shadow stack backtrace.
> 
> Hello Christina,
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> > Thanks a lot for your feedback.
> 
> You're welcome! Thank you for moving this forward. Sorry for the big delays
> on my side. More things going on at once than I'd like.
> 
> > For the hook gdbarch_get_shadow_stack_size I still need your GCS
> > implementation. 😊
> > I suggest a separate patch for this with you as the only author and
> > I'll include this in my v3 then, too. Does that make sense?
> 
> I think you also need aarch64_linux_is_no_return_shadow_stack_address,
> right?  I'm including both in a a patch at the end of this email.
> 
> There are FIXMEs in them to remind myself to do something better than
> assert if ssp is an invalid address. Perhaps throw an error as you mention
> below.
> 
> Also, feel free to not provide the aarch64 versions of the hooks in this patch
> and the following one. I was planning to send them after your series goes in.
>
> The reason I wanted an aarch64 hook in the first patch was so that existing
> GCS functionality doesn't regress, but the hooks in the other patches are for
> new functionality so it's fine to have only the amd64 implementation.

Yes, of course. I only realized this now... It's probably a good idea to simply create
a new aarch64 specific series once the one for amd64 is in.
But still, it's interesting to see how you implement that for GCS, so thanks for sharing.

I'll not include the patches in my v3 then, expect for the first one. Even though the GCS
feature should not break without it, I think it's good to have it all together in this case.

> >> > +  const unsigned long shadow_stack_bytes = range.second - *ssp;
> >> > +
> >> > +  gdb_assert ((shadow_stack_bytes % 8) == 0);
> >>
> >> I don't think this should be an assert. If it fails, it triggers an
> >> internal error in GDB.  In this case it could indeed mean an internal
> >> error (GDB somehow got the SSP or range wrong), but it could also be
> >> (and probably more likely) an inconsistent state of the inferior.
> >> This can happen in a program being debugged so GDB should be able to
> >> handle it gracefully, and if possible provide useful information to the user.
> >
> > I agree. This is rather something that is outside GDB's control.
> >
> > From the documentation for internal errors:
> > "Internal errors indicate programming errors such as assertion failures, as
> opposed to
> >    more general errors beyond the application's control.  "
> >
> > So based on that I rather would choose a normal error, not an internal error.
> > What do you think ?
> 
> The effect of the error being thrown would be just that the "bt -shadow"
> command is interrupted, right? If so, I think it's a good idea.

Yes, at the moment we call this function only in
get_trailing_outermost_shadow_stack_frame_info (before we printed any frame).

I'd suggest something like: "Invalid shadow stack state." This should be generic enough
if this hook is ever called somewhere else. 

Christina

> >> > +  return shadow_stack_bytes / 8;
> >> > +}
> 
> --
> Thiago
> 
> From 89f07938071f78c479ac045296afd562ab9ef93a Mon Sep 17 00:00:00
> 2001
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Date: Tue, 10 Mar 2026 21:44:35 -0300
> Subject: [PATCH] WIP GDB: aarch64-linux: Add gdbarch hooks for printing
> shadow  stack
> 
> Enables bt -shadow.
> 
> There are still some FIXMEs to address.
> ---
>  gdb/aarch64-linux-tdep.c | 38
> ++++++++++++++++++++++++++++++++++++++
>  gdb/aarch64-tdep.c       | 20 ++++++++++++++++++++
>  gdb/arch/aarch64.h       |  3 +++
>  3 files changed, 61 insertions(+)
> 
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c index
> f37b28067b8a..e9c9b8480aac 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -2615,6 +2615,42 @@ aarch64_linux_get_shadow_stack_pointer
> (gdbarch *gdbarch, regcache *regcache,
>    return gcspr;
>  }
> 
> +/* 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: a signal handling specific GCS cap token will be
> +   written to the GCS.  In case this is true, configure the string which
> +   describes the frame and is displayed instead of the address in the
> +   shadow stack backtrace.  */
> +
> +static bool
> +aarch64_linux_is_no_return_shadow_stack_address
> +  (gdbarch *gdbarch,
> +   const shadow_stack_frame_info &frame,
> +   std::string &frame_type)
> +{
> +  /* FRAME must be a valid shadow stack frame.  */
> +  bool valid_addr
> +    = gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> frame.ssp,
> +						    nullptr);
> +  /* FIXME: Shouldn't be an assert.  */
> +  gdb_assert (valid_addr == true);
> +
> +  /* If the GCS entry isn't a cap token, then it should be a return
> +     address.  */
> +  if ((frame.value & AARCH64_GCS_CAP_ADDR_MASK) != frame.ssp)
> +    return false;
> +
> +  /* When delivering a signal, the Linux kernel writes a cap token with the
> +     token type (bits 0..11) all clear.  */  if ((frame.value &
> + AARCH64_GCS_CAP_TOKEN_MASK) == 0)
> +    {
> +      frame_type = _("<sigframe token>");
> +      return true;
> +    }
> +
> +  return false;
> +}
> +
>  /* AArch64 Linux implementation of the report_signal_info gdbarch
>     hook.  Displays information about possible memory tag violations.  */
> 
> @@ -3193,6 +3229,8 @@ aarch64_linux_init_abi (struct gdbarch_info info,
> struct gdbarch *gdbarch)
>      {
>        set_gdbarch_get_shadow_stack_pointer (gdbarch,
> 
> 	aarch64_linux_get_shadow_stack_pointer);
> +      set_gdbarch_is_no_return_shadow_stack_address (gdbarch,
> +
> aarch64_linux_is_no_return_shadow_stack_address);
>        tdep->fn_prev_gcspr = dwarf2_prev_ssp;
>      }
>  }
> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index
> 112d42c6a1ac..6bf63bcc1f97 100644
> --- a/gdb/aarch64-tdep.c
> +++ b/gdb/aarch64-tdep.c
> @@ -1926,6 +1926,24 @@ aarch64_top_addr_empty_shadow_stack
> (gdbarch *gdbarch, const CORE_ADDR addr,
>    return addr >= range.second - 8;
>  }
> 
> +/* Return the number of elements which are currently on the shadow stack
> +   based on the shadow stack memory RANGE [start_address, end_address)
> +   of the current thread.  In case shadow stack is not enabled for the
> +   current thread, return -1.  */
> +
> +static long
> +aarch64_get_shadow_stack_size (gdbarch *gdbarch, const CORE_ADDR ssp,
> +			       const std::pair<CORE_ADDR, CORE_ADDR> range)
> {
> +  const unsigned long shadow_stack_bytes = range.second - ssp;
> +
> +  /* FIXME: Shouldn't be an assert.  */  gdb_assert
> + ((shadow_stack_bytes % 8) == 0);
> +
> +  /* The oldest entry in the GCS isn't an address, just the value '0'.
> +*/
> +  return shadow_stack_bytes / 8 - 1;
> +}
> +
>  /* Implement the "push_dummy_call" gdbarch method.  */
> 
>  static CORE_ADDR
> @@ -4812,6 +4830,8 @@ aarch64_gdbarch_init (struct gdbarch_info info,
> struct gdbarch_list *arches)
>        set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
>        set_gdbarch_top_addr_empty_shadow_stack
>  	(gdbarch, aarch64_top_addr_empty_shadow_stack);
> +      set_gdbarch_get_shadow_stack_size (gdbarch,
> +					 aarch64_get_shadow_stack_size);
>      }
> 
>    /* ABI */
> diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h index
> 0e9715a4268a..7c69bfd01feb 100644
> --- a/gdb/arch/aarch64.h
> +++ b/gdb/arch/aarch64.h
> @@ -246,6 +246,9 @@ enum aarch64_regnum
>  /* Size of the SME2 ZT0 register in bytes.  */  #define
> AARCH64_SME2_ZT0_SIZE 64
> 
> +#define AARCH64_GCS_CAP_TOKEN_MASK ((uint64_t) 0xFFF) #define
> +AARCH64_GCS_CAP_ADDR_MASK ~AARCH64_GCS_CAP_TOKEN_MASK
> +
>  /* Feature check for Floating Point Mode Register.  */  #ifndef
> HWCAP2_FPMR  #define HWCAP2_FPMR (1ULL << 48)
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

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

* RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-02-13 16:42     ` Schimpe, Christina
@ 2026-04-14  8:43       ` Schimpe, Christina
  2026-04-14 11:53         ` Eli Zaretskii
  0 siblings, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-14  8:43 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Schimpe, Christina
> Sent: Freitag, 13. Februar 2026 17:42
> To: 'Eli Zaretskii' <eliz@gnu.org>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print
> the shadow stack backtrace.
> 
> > -----Original Message-----
> > From: Eli Zaretskii <eliz@gnu.org>
> > Sent: Freitag, 23. Januar 2026 09:52
> > To: Schimpe, Christina <christina.schimpe@intel.com>
> > Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> > Subject: Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to
> > print the shadow stack backtrace.
> >
> > > From: Christina Schimpe <christina.schimpe@intel.com>
> > > Cc: thiago.bauermann@linaro.org
> > > Date: Fri, 23 Jan 2026 08:05:28 +0000
> > >
> > >  gdb/NEWS                                      |   3 +
> > >  gdb/amd64-linux-tdep.c                        |  26 +
> > >  gdb/annotate.c                                |  93 +++-
> > >  gdb/annotate.h                                |  18 +-
> > >  gdb/doc/gdb.texinfo                           |  27 +
> > >  gdb/gdbarch-gen.c                             |  32 ++
> > >  gdb/gdbarch-gen.h                             |  15 +-
> > >  gdb/gdbarch_components.py                     |  20 +-
> > >  gdb/shadow-stack.c                            | 480 +++++++++++++++++-
> > >  gdb/shadow-stack.h                            |   6 +
> > >  gdb/stack.c                                   |  73 +--
> > >  gdb/stack.h                                   |  55 ++
> > >  .../gdb.arch/amd64-shadow-stack-cmds.exp      |  88 ++++
> > >  gdb/testsuite/gdb.base/options.exp            |   2 +-
> > >  14 files changed, 875 insertions(+), 63 deletions(-)
> >
> > Thanks.
> >
> > > diff --git a/gdb/NEWS b/gdb/NEWS
> > > index 74fc353d7e9..37b3add11ed 100644
> > > --- a/gdb/NEWS
> > > +++ b/gdb/NEWS
> > > @@ -3,6 +3,9 @@
> > >
> > >  *** Changes since GDB 17
> > >
> > > +* New "-shadow" command line option for the backtrace command to
> > > +print the
> > > +  shadow stack backtrace instead of the normal backtrace.
> > > +
> > >  * Support for .gdb_index sections with version less than 7 has been
> > >    removed.
> >
> > This part is okay.
> >
> > > +A shadow stack is supported, for instance, with the Intel
> > > +Control-Flow Enforcement Technology (@xref{CET}) on x86 and the
> > > +Guarded Control Stack feature (@xref{GCS}) on AArch64.
> >             ^^^^^^^^^^
> > This should use @pxref, not @xref.
> 
> Yes, thanks!
> 
> > > +This option may be combined with “-frame-info” and implies ‘-no-filters’
> > > +and ‘-frame-arguments none’.     ^^^^^^^^^^^^^             ^^^^^^^^^^^^^
> >        ^^^^^^^^^^^^^^^^^^^^^^^
> > All those should use @code and without the quotes.
> 
> Yes, will fix, too.
> 
> > > +@node GCS
> > >  @subsubsection AArch64 Guarded Control Stack  @cindex Guarded
> > Control
> > > Stack, AArch64  @cindex GCS, AArch64 @@ -27248,6 +27274,7 @@
> > registers
> > >
> > >  @end itemize
> > >
> > > +@node CET
> > >  @subsubsection Intel Control-Flow Enforcement Technology.
> > >  @cindex Intel Control-Flow Enforcement Technology.
> >
> > You are adding @node's, but AFAICT you don't change the @menu of the
> > parent section.  That can only work with recent Texinfo versions,
> > where @menus are updated automatically.  Please update the parent
> > @menu as part of the patch, to not rely on those recent versions.
> > Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> 
> Ah, I wasn't aware, thanks!
> 
> Now it looks like
> @menu
> * AArch64::
> * GCS::
> * x86::
> * CET::
> * Alpha::
> [...]
> @end menu
> 
> This looks somehow unconventional since CET/GCS are subsubsections (and
> no subsections like AArch64/x86) but since this is a requirement of old
> texinfo versions I'll simply add it.

Hi Eli, 

I just noticed the following warnings on fedora with texinfo 7.2:

gdb.texinfo:26867: warning: node next pointer for `AArch64' is `x86' but next is `GCS' in menu
gdb.texinfo:27232: warning: node `x86' is next for `GCS' in menu but not in sectioning
gdb.texinfo:27232: warning: node `AArch64' is prev for `GCS' in menu but not in sectioning
gdb.texinfo:27232: warning: node up pointer for `GCS' is `AArch64' but up is `Architectures' in menu
gdb.texinfo:27280: warning: node next pointer for `x86' is `Alpha' but next is `CET' in menu
gdb.texinfo:27280: warning: node prev pointer for `x86' is `AArch64' but prev is `GCS' in menu
gdb.texinfo:27321: warning: node `Alpha' is next for `CET' in menu but not in sectioning
gdb.texinfo:27321: warning: node `x86' is prev for `CET' in menu but not in sectioning
gdb.texinfo:27321: warning: node up pointer for `CET' is `x86' but up is `Architectures' in menu
gdb.texinfo:27362: warning: node prev pointer for `Alpha' is `x86' but prev is `CET' in menu

for the menu changes:
~~~
@menu
 * AArch64::
+* GCS::
 * x86::
+* CET::
 * Alpha::
 * MIPS::
 * HPPA::
~~~

This did not appear when I compiled this on different systems
(ubuntu texinfo version 6.8 or 7.1), otherwise, I'd have noticed it earlier.

If I change the CET and GCS subsubsections to subsections the warning disappears...
so I think I must add a new @menu for the subsubsections.

+@menu
+* CET::
+@end menu
+
 @node CET

This avoids triggering a warning. However, since none of the other AArch64 or
x86 subsubsections currently have nodes, I believe we should add nodes for
them and update the menu accordingly:

 @end table

+@menu
+* SVE::
+* SME::
+* SME2::
+* PAC::
+* MTE::
+* GCS::
+@end menu
+
+@node SVE
 @subsubsection AArch64 Scalable Vector Extension
 @cindex Scalable Vector Extension, AArch64
 @cindex SVE, AArch64
@@ -26941,6 +26951,7 @@ internally by @value{GDBN} and the Linux Kernel.

 @end itemize

+@node SME

[...]

Please let me know if this works for you.

Thanks,
Christina

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

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

* Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-04-14  8:43       ` Schimpe, Christina
@ 2026-04-14 11:53         ` Eli Zaretskii
  2026-04-14 13:28           ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Eli Zaretskii @ 2026-04-14 11:53 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.bauermann@linaro.org" <thiago.bauermann@linaro.org>
> Date: Tue, 14 Apr 2026 08:43:52 +0000
> 
> I just noticed the following warnings on fedora with texinfo 7.2:
> 
> gdb.texinfo:26867: warning: node next pointer for `AArch64' is `x86' but next is `GCS' in menu
> gdb.texinfo:27232: warning: node `x86' is next for `GCS' in menu but not in sectioning
> gdb.texinfo:27232: warning: node `AArch64' is prev for `GCS' in menu but not in sectioning
> gdb.texinfo:27232: warning: node up pointer for `GCS' is `AArch64' but up is `Architectures' in menu
> gdb.texinfo:27280: warning: node next pointer for `x86' is `Alpha' but next is `CET' in menu
> gdb.texinfo:27280: warning: node prev pointer for `x86' is `AArch64' but prev is `GCS' in menu
> gdb.texinfo:27321: warning: node `Alpha' is next for `CET' in menu but not in sectioning
> gdb.texinfo:27321: warning: node `x86' is prev for `CET' in menu but not in sectioning
> gdb.texinfo:27321: warning: node up pointer for `CET' is `x86' but up is `Architectures' in menu
> gdb.texinfo:27362: warning: node prev pointer for `Alpha' is `x86' but prev is `CET' in menu
> 
> for the menu changes:
> ~~~
> @menu
>  * AArch64::
> +* GCS::
>  * x86::
> +* CET::
>  * Alpha::
>  * MIPS::
>  * HPPA::
> ~~~

Yes, because you are adding these items to the wrong menu.  These
nodes are NOT subsections of "Architectures", they are subsubsections
of its subsections.  So the @menu with these nodes should be in the
parent subsections, not in "Architectures".

> If I change the CET and GCS subsubsections to subsections the warning disappears...
> so I think I must add a new @menu for the subsubsections.
> 
> +@menu
> +* CET::
> +@end menu

Yes.

> This avoids triggering a warning. However, since none of the other AArch64 or
> x86 subsubsections currently have nodes, I believe we should add nodes for
> them and update the menu accordingly:

There's no need to add a node for each subsubsection.  In fact, I'm
not sure I understand why you wanted to add the two @node's that give
you trouble, can you explain?

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

* RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-04-14 11:53         ` Eli Zaretskii
@ 2026-04-14 13:28           ` Schimpe, Christina
  2026-04-14 14:12             ` Eli Zaretskii
  0 siblings, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-14 13:28 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Eli Zaretskii <eliz@gnu.org>
> Sent: Dienstag, 14. April 2026 13:53
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print
> the shadow stack backtrace.
> 
> > From: "Schimpe, Christina" <christina.schimpe@intel.com>
> > CC: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>,
> > 	"thiago.bauermann@linaro.org" <thiago.bauermann@linaro.org>
> > Date: Tue, 14 Apr 2026 08:43:52 +0000
> >
> > I just noticed the following warnings on fedora with texinfo 7.2:
> >
> > gdb.texinfo:26867: warning: node next pointer for `AArch64' is `x86'
> > but next is `GCS' in menu
> > gdb.texinfo:27232: warning: node `x86' is next for `GCS' in menu but
> > not in sectioning
> > gdb.texinfo:27232: warning: node `AArch64' is prev for `GCS' in menu
> > but not in sectioning
> > gdb.texinfo:27232: warning: node up pointer for `GCS' is `AArch64' but
> > up is `Architectures' in menu
> > gdb.texinfo:27280: warning: node next pointer for `x86' is `Alpha' but
> > next is `CET' in menu
> > gdb.texinfo:27280: warning: node prev pointer for `x86' is `AArch64'
> > but prev is `GCS' in menu
> > gdb.texinfo:27321: warning: node `Alpha' is next for `CET' in menu but
> > not in sectioning
> > gdb.texinfo:27321: warning: node `x86' is prev for `CET' in menu but
> > not in sectioning
> > gdb.texinfo:27321: warning: node up pointer for `CET' is `x86' but up
> > is `Architectures' in menu
> > gdb.texinfo:27362: warning: node prev pointer for `Alpha' is `x86' but
> > prev is `CET' in menu
> >
> > for the menu changes:
> > ~~~
> > @menu
> >  * AArch64::
> > +* GCS::
> >  * x86::
> > +* CET::
> >  * Alpha::
> >  * MIPS::
> >  * HPPA::
> > ~~~
> 
> Yes, because you are adding these items to the wrong menu.  These nodes are
> NOT subsections of "Architectures", they are subsubsections of its
> subsections.  So the @menu with these nodes should be in the parent
> subsections, not in "Architectures".

It appears there was a misunderstanding on my part—thank you for the clarification.
I thought you meant that menu with "parent menu" in your feedback before,
since there is currently no menu in the parent subsections.

> > If I change the CET and GCS subsubsections to subsections the warning
> disappears...
> > so I think I must add a new @menu for the subsubsections.
> >
> > +@menu
> > +* CET::
> > +@end menu
> 
> Yes.
> 
> > This avoids triggering a warning. However, since none of the other
> > AArch64 or
> > x86 subsubsections currently have nodes, I believe we should add nodes
> > for them and update the menu accordingly:
> 
> There's no need to add a node for each subsubsection. 

Won't this help us to navigate using 'previous' and 'next' etc. when using "info gdb" ?

>  In fact, I'm not sure I
> understand why you wanted to add the two @node's that give you trouble,
> can you explain?

You mean the nodes for CET and GCS? 

If I don't add them, I'll see 
~~~
gdb.texinfo:8801: @pxref reference to nonexistent node `CET'
gdb.texinfo:8822: @pxref reference to nonexistent node `CET'
~~~

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

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

* Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-04-14 13:28           ` Schimpe, Christina
@ 2026-04-14 14:12             ` Eli Zaretskii
  2026-04-14 15:05               ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Eli Zaretskii @ 2026-04-14 14:12 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.bauermann@linaro.org" <thiago.bauermann@linaro.org>
> Date: Tue, 14 Apr 2026 13:28:52 +0000
> 
> > > This avoids triggering a warning. However, since none of the other
> > > AArch64 or
> > > x86 subsubsections currently have nodes, I believe we should add nodes
> > > for them and update the menu accordingly:
> > 
> > There's no need to add a node for each subsubsection. 
> 
> Won't this help us to navigate using 'previous' and 'next' etc. when using "info gdb" ?

It might, but with subsubsections that are not too large, is that a
real need?  It's up to you, of course, so if you want to add nodes, go
ahead.  Just make sure to create/update the menus accordingly.

> >  In fact, I'm not sure I
> > understand why you wanted to add the two @node's that give you trouble,
> > can you explain?
> 
> You mean the nodes for CET and GCS? 
> 
> If I don't add them, I'll see 
> ~~~
> gdb.texinfo:8801: @pxref reference to nonexistent node `CET'
> gdb.texinfo:8822: @pxref reference to nonexistent node `CET'

You can use @anchor to have a place to refer to in a cross-reference.

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

* RE: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
  2026-04-14 14:12             ` Eli Zaretskii
@ 2026-04-14 15:05               ` Schimpe, Christina
  0 siblings, 0 replies; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-14 15:05 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Eli Zaretskii <eliz@gnu.org>
> Sent: Dienstag, 14. April 2026 16:13
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print
> the shadow stack backtrace.
> 
> > From: "Schimpe, Christina" <christina.schimpe@intel.com>
> > CC: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>,
> > 	"thiago.bauermann@linaro.org" <thiago.bauermann@linaro.org>
> > Date: Tue, 14 Apr 2026 13:28:52 +0000
> >
> > > > This avoids triggering a warning. However, since none of the other
> > > > AArch64 or
> > > > x86 subsubsections currently have nodes, I believe we should add
> > > > nodes for them and update the menu accordingly:
> > >
> > > There's no need to add a node for each subsubsection.
> >
> > Won't this help us to navigate using 'previous' and 'next' etc. when using
> "info gdb" ?
> 
> It might, but with subsubsections that are not too large, is that a real need?
> It's up to you, of course, so if you want to add nodes, go ahead.  Just make
> sure to create/update the menus accordingly.

If we do that, a separate patch is probably better.  But given that we can use
@anchor instead, I agree we don't necessarily have to add the menu.
I missed that we have this alternative.

> > >  In fact, I'm not sure I
> > > understand why you wanted to add the two @node's that give you
> > > trouble, can you explain?
> >
> > You mean the nodes for CET and GCS?
> >
> > If I don't add them, I'll see
> > ~~~
> > gdb.texinfo:8801: @pxref reference to nonexistent node `CET'
> > gdb.texinfo:8822: @pxref reference to nonexistent node `CET'
> 
> You can use @anchor to have a place to refer to in a cross-reference.

I will use that one now.

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

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

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-04-09  9:49           ` Schimpe, Christina
@ 2026-04-14 17:34             ` Tom Tromey
  2026-04-15  7:35               ` Schimpe, Christina
  0 siblings, 1 reply; 49+ messages in thread
From: Tom Tromey @ 2026-04-14 17:34 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Tom Tromey, gdb-patches, thiago.bauermann

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

> Sorry for getting back delayed response. I am seeing the following
> when running check-gdbarch.py:

> ~~~
> $ ./check-gdbarch.py
> never set: shadow_stack_element_size_aligned
> ~~~

> Given that I assign a pre-default I don't have to configure it for amd64 specifically.
> But starting with this patch, this message will always appear when running the script. 

> Should we change the script for this case?

It depends.

This means that no architecture sets this gdbarch parameter.
So:

1. Maybe it's not needed at all and should be removed, or
2. Maybe it's used in some later patch and the warning should be
   ignored, or
3. Maybe it might be used in the future and we don't want to
   hard-code the value and so it should be marked 'unused'
   in gdbarch_components.py

Tom

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

* RE: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-04-14 17:34             ` Tom Tromey
@ 2026-04-15  7:35               ` Schimpe, Christina
  2026-04-15 15:54                 ` Tom Tromey
  0 siblings, 1 reply; 49+ messages in thread
From: Schimpe, Christina @ 2026-04-15  7:35 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, thiago.bauermann

> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Dienstag, 14. April 2026 19:34
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: Tom Tromey <tom@tromey.com>; gdb-patches@sourceware.org;
> thiago.bauermann@linaro.org
> Subject: Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack
> pointer.
> 
> >>>>> Schimpe, Christina <christina.schimpe@intel.com> writes:
> 
> > Sorry for getting back delayed response. I am seeing the following
> > when running check-gdbarch.py:
> 
> > ~~~
> > $ ./check-gdbarch.py
> > never set: shadow_stack_element_size_aligned ~~~
> 
> > Given that I assign a pre-default I don't have to configure it for amd64
> specifically.
> > But starting with this patch, this message will always appear when running
> the script.
> 
> > Should we change the script for this case?
> 
> It depends.
> 
> This means that no architecture sets this gdbarch parameter.
> So:
> 
> 1. Maybe it's not needed at all and should be removed, or 2. Maybe it's used
> in some later patch and the warning should be
>    ignored, or
> 3. Maybe it might be used in the future and we don't want to
>    hard-code the value and so it should be marked 'unused'
>    in gdbarch_components.py

It is currently not used in any subsequent patch. GCS, amd64, and x32 all use an 8‑byte offset.
In theory, Linux kernel support for 32‑bit shadow stacks could be added in the future, which
would require a 4‑byte offset. For other architectures, I don't know about the details.

So based on that I'd go with option 3, do you agree ?

I could add a comment such as:

+    # Currently unused but we wanted to keep this hook around,
+    # since x86 32-bit shadow stacks would require a 4-byte offset,
+    # for instance.  But this is not supported yet by the Linux kernel
+    # as x86 shadow stack for user space is only supported for amd64
+    # Linux starting with Linux kernel v6.6.
+    unused=True,

Alternatively, I could re-use the already existing shorter comment if you prefer:

+    # Currently unused but we wanted to keep this hook around.
+    unused=True,

I'd prefer the first comment, but keeping it short would be fine with me, too. 

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

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

* Re: [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer.
  2026-04-15  7:35               ` Schimpe, Christina
@ 2026-04-15 15:54                 ` Tom Tromey
  0 siblings, 0 replies; 49+ messages in thread
From: Tom Tromey @ 2026-04-15 15:54 UTC (permalink / raw)
  To: Schimpe, Christina; +Cc: Tom Tromey, gdb-patches, thiago.bauermann

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

>> 3. Maybe it might be used in the future and we don't want to
>> hard-code the value and so it should be marked 'unused'
>> in gdbarch_components.py

> So based on that I'd go with option 3, do you agree ?

It seems fine to me.

> I could add a comment such as:

> +    # Currently unused but we wanted to keep this hook around,
> +    # since x86 32-bit shadow stacks would require a 4-byte offset,
> +    # for instance.  But this is not supported yet by the Linux kernel
> +    # as x86 shadow stack for user space is only supported for amd64
> +    # Linux starting with Linux kernel v6.6.
> +    unused=True,


> I'd prefer the first comment, but keeping it short would be fine with me, too. 

I agree, sounds good.  Thank you.

Tom

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

end of thread, other threads:[~2026-04-15 15:54 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-23  8:05 [PATCH v2 0/9] Add new command to print the shadow stack backtrace Christina Schimpe
2026-01-23  8:05 ` [PATCH v2 1/9] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
2026-02-19 17:55   ` Tom Tromey
2026-02-27 18:09     ` Schimpe, Christina
2026-02-27 18:26       ` Tom Tromey
2026-03-02 11:53         ` Schimpe, Christina
2026-04-09  9:49           ` Schimpe, Christina
2026-04-14 17:34             ` Tom Tromey
2026-04-15  7:35               ` Schimpe, Christina
2026-04-15 15:54                 ` Tom Tromey
2026-02-27 22:54       ` Thiago Jung Bauermann
2026-03-06  3:15   ` Thiago Jung Bauermann
2026-03-06  3:57     ` Thiago Jung Bauermann
2026-04-09 11:57       ` Schimpe, Christina
2026-04-10  5:03         ` Thiago Jung Bauermann
2026-04-10  7:53           ` Schimpe, Christina
2026-04-09 12:06   ` Schimpe, Christina
2026-04-10  5:05     ` Thiago Jung Bauermann
2026-01-23  8:05 ` [PATCH v2 2/9] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
2026-01-23  8:05 ` [PATCH v2 3/9] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
2026-01-23  8:05 ` [PATCH v2 4/9] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
2026-02-19 17:32   ` Tom Tromey
2026-04-09 12:40     ` Schimpe, Christina
2026-01-23  8:05 ` [PATCH v2 5/9] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
2026-01-23  8:05 ` [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
2026-01-23  8:52   ` Eli Zaretskii
2026-02-13 16:42     ` Schimpe, Christina
2026-04-14  8:43       ` Schimpe, Christina
2026-04-14 11:53         ` Eli Zaretskii
2026-04-14 13:28           ` Schimpe, Christina
2026-04-14 14:12             ` Eli Zaretskii
2026-04-14 15:05               ` Schimpe, Christina
2026-02-19 18:19   ` Tom Tromey
2026-04-09 16:48     ` Schimpe, Christina
2026-03-06  4:31   ` Thiago Jung Bauermann
2026-03-06  9:39     ` Schimpe, Christina
2026-04-09 15:12     ` Schimpe, Christina
2026-04-10  6:21       ` Thiago Jung Bauermann
2026-04-10 12:12         ` Schimpe, Christina
2026-01-23  8:05 ` [PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
2026-01-23  8:47   ` Eli Zaretskii
2026-02-19 17:41   ` Tom Tromey
2026-01-23  8:05 ` [PATCH v2 8/9] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
2026-02-19 17:43   ` Tom Tromey
2026-01-23  8:05 ` [PATCH v2 9/9] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
2026-01-23  8:46   ` Eli Zaretskii
2026-02-13 19:17     ` Schimpe, Christina
2026-02-19 18:26   ` Tom Tromey
2026-03-02 12:39 ` [PATCH v2 0/9] Add new command to print the shadow stack backtrace Schimpe, Christina

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox