Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Christina Schimpe <christina.schimpe@intel.com>
To: gdb-patches@sourceware.org
Cc: thiago.bauermann@linaro.org
Subject: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
Date: Fri, 23 Jan 2026 08:05:28 +0000	[thread overview]
Message-ID: <20260123080532.878738-7-christina.schimpe@intel.com> (raw)
In-Reply-To: <20260123080532.878738-1-christina.schimpe@intel.com>

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

  parent reply	other threads:[~2026-01-23  8:10 UTC|newest]

Thread overview: 49+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-23  8:05 [PATCH v2 0/9] Add new command " 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 ` Christina Schimpe [this message]
2026-01-23  8:52   ` [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace 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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260123080532.878738-7-christina.schimpe@intel.com \
    --to=christina.schimpe@intel.com \
    --cc=gdb-patches@sourceware.org \
    --cc=thiago.bauermann@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox