Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Christina Joos <christina.joos@intel.com>
To: gdb-patches@sourceware.org
Cc: thiago.bauermann@linaro.org, tom@tromey.com, luis.machado.foss@gmail.com
Subject: [PATCH v5 13/13] gdb, mi: Add -shadow-stack-list-frames command
Date: Thu, 24 Sep 2026 10:33:10 +0200	[thread overview]
Message-ID: <20260924083311.1961530-14-christina.joos@intel.com> (raw)
In-Reply-To: <20260924083311.1961530-1-christina.joos@intel.com>

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

Example print of a full shadow stack backtrace:
~~~
(gdb)
-shadow-stack-list-frames
^done,shadow-stack=[
shadow-stack-frame={level="0",addr="0x00007ffff7c3fe70",
  func="__libc_start_call_main",file="../sysdeps/nptl/libc_start_call_main.h",
  fullname="/usr/[...]/sysdeps/nptl/libc_start_call_main.h",
  line="58",arch="i386:x86-64"},
shadow-stack-frame={level="1",addr="0x00007ffff7c3ff20",
  func="__libc_start_main_impl",file="../csu/libc-start.c",
  fullname="/usr/[...]/csu/libc-start.c",
  line="128",arch="i386:x86-64"},
shadow-stack-frame={level="2",addr="0x0000000000401075",
  func="_start",arch="i386:x86-64"}]
~~~

Example print of a shadow stack backtrace using low- and high-frame:
~~~
(gdb)
-shadow-stack-list-frames 0 1
^done,shadow-stack=[
shadow-stack-frame={level="0",addr="0x00007ffff7c3fe70",
  func="__libc_start_call_main",file="../sysdeps/nptl/libc_start_call_main.h",
  fullname="/usr/[...]/sysdeps/nptl/libc_start_call_main.h",
  line="58",arch="i386:x86-64"},
shadow-stack-frame={level="1",addr="0x00007ffff7c3ff20",
  func="__libc_start_main_impl",file="../csu/libc-start.c",
  fullname="/usr/[...]/csu/libc-start.c",
  line="128",arch="i386:x86-64"}]
~~~
---
 gdb/NEWS                                      |   8 ++
 gdb/doc/gdb.texinfo                           |  51 +++++++
 gdb/mi/mi-cmd-stack.c                         |  95 +++++++++++++
 gdb/mi/mi-cmds.c                              |   2 +
 gdb/mi/mi-cmds.h                              |   1 +
 gdb/shadow-stack.c                            | 127 +++++++++++++-----
 gdb/shadow-stack.h                            |  43 ++++++
 .../gdb.mi/mi-shadow-stack-signal.exp         |  69 ++++++++++
 gdb/testsuite/gdb.mi/mi-shadow-stack.exp      |  93 +++++++++++++
 9 files changed, 453 insertions(+), 36 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
 create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 707f3b5327e..52edd6de239 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -391,6 +391,14 @@ qExecAndArgs
   AIX version is now AIX 7.2 TL5.  GDB will only support
   DWARF debugging in AIX, going forward.
 
+* New MI commands
+
+-shadow-stack-list-frames
+  Added new MI command which is equivalent to the CLI command
+  'backtrace -shadow' but supports 'low-frame' and 'high-frame' as
+  command line parameters.  The parameters are used to print shadow
+  stack frames between 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 c2f9474b4de..4558a4a523c 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -35585,6 +35585,57 @@ Show a single frame:
 (gdb)
 @end smallexample
 
+@anchor{-shadow-stack-list-frames}
+@findex -shadow-stack-list-frames
+@subheading The @code{-shadow-stack-list-frames} Command
+
+@subsubheading Synopsis
+
+@smallexample
+ -shadow-stack-list-frames [ @var{low-frame} @var{high-frame} ]
+@end smallexample
+
+List the shadow stack frames currently on the shadow stack.  In case the
+element on the shadow stack is a return address, @value{GDBN} prints the
+same fields as in @code{-stack-list-frames} with the return address on
+the shadow stack as @var{addr}.
+If the element on the shadow stack is not a return address, @value{GDBN}
+only prints @var{level}, @var{addr}, @var{func} and @var{arch}.
+
+If invoked without arguments, this command prints a backtrace for the
+whole shadow stack.  Like the @code{-stack-list-frames} command, if given
+two integer arguments, it shows the frames whose levels are between the
+two arguments (inclusive).
+
+@subsubheading @value{GDBN} Command
+
+The corresponding @value{GDBN} command is @samp{backtrace -shadow}.
+
+@subsubheading Example
+
+Show shadow stack frames between @var{low-frame} and @var{high-frame}:
+
+@smallexample
+(gdb)
+-shadow-stack-list-frames 0 2
+^done,shadow-stack=[
+shadow-stack-frame=@{level="0",addr="0x00007ffff7c45330",
+  func="<signal handler called>",arch="i386:x86-64"@},
+shadow-stack-frame=@{level="1",addr="0x80007ffff7bfffd8",
+  func="<sigframe token>",arch="i386:x86-64"@},
+shadow-stack-frame=@{level="2",addr="0x00007ffff7c4527e",
+  func="__GI_raise",file="[...]/raise.c",
+  fullname="./[...]/posix/raise.c",
+  line="26",arch="i386:x86-64"@}]
+(gdb)
+@end smallexample
+
+Note that for frame 0 and 1, @value{GDBN} printed only @var{level},
+@var{addr}, @var{func} and @var{arch}.  For frame 0, @var{func} is
+@code{"<signal handler called>"} similar to the @code{-stack-list-frames}
+command.  For frame 1 @value{GDBN} prints @code{"<sigframe token>"} for
+@var{func}.  This is due to an element on the shadow stack which is not a
+return address.
 
 @findex -stack-list-locals
 @anchor{-stack-list-locals}
diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c
index a3b577032ce..c127fc9d239 100644
--- a/gdb/mi/mi-cmd-stack.c
+++ b/gdb/mi/mi-cmd-stack.c
@@ -34,6 +34,9 @@
 #include "inferior.h"
 #include "source.h"
 #include "gdbsupport/unordered_map.h"
+#include "shadow-stack.h"
+#include "gdbarch.h"
+#include "arch-utils.h"
 
 enum what_to_list { locals, arguments, all };
 
@@ -830,3 +833,95 @@ mi_cmd_stack_info_frame (const char *command, const char *const *argv,
   print_frame_info (user_frame_print_options,
 		    get_selected_frame (), 1, LOC_AND_ADDRESS, 0, 1);
 }
+
+/* Parse arguments of -shadow-stack-list-frames command and set FRAME_LOW
+   and FRAME_HIGH accordingly.  Throw an error in case the arguments are
+   invalid.  */
+
+static void
+mi_cmd_shadow_stack_list_frames_parse_args (const char *const *argv,
+					    int argc, int &frame_low,
+					    int &frame_high)
+{
+  /* 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]);
+      if (frame_low < 0 && frame_high < 0)
+	error (_("-shadow-stack-list-frames: Invalid option " \
+		 "``%d'' and ``%d''."),
+	       frame_low, frame_high);
+      else if (frame_low < 0)
+	error (_("-shadow-stack-list-frames: Invalid option ``%d''."),
+	       frame_low);
+      else if (frame_high < 0)
+	error (_("-shadow-stack-list-frames: Invalid option ``%d''."),
+	       frame_high);
+    }
+  else
+    {
+      /* No arguments, print the whole shadow stack backtrace.  */
+      frame_low = -1;
+      frame_high = -1;
+    }
+}
+
+/* Print a list of the shadow stack frames.  Args can be none, in which
+   case we want to print the whole shadow stack backtrace, or a pair of
+   numbers specifying the frame numbers at which to start and stop the
+   display.  If the two numbers are equal, a single frame will be
+   displayed.  */
+
+void
+mi_cmd_shadow_stack_list_frames (const char *command,
+				 const char *const *argv,
+				 int argc)
+{
+  int frame_low;
+  int frame_high;
+
+  mi_cmd_shadow_stack_list_frames_parse_args (argv, argc, frame_low,
+					      frame_high);
+
+  gdbarch *gdbarch = get_current_arch ();
+  const CORE_ADDR start_ssp
+    = get_validated_shadow_stack_pointer (gdbarch);
+
+  ui_out_emit_list list_emitter (current_uiout, "shadow-stack");
+
+  std::optional<std::pair<CORE_ADDR, CORE_ADDR>> range
+    = get_non_empty_shadow_stack_range (gdbarch, start_ssp);
+  if (!range.has_value ())
+    return;
+
+  /* Extract the first shadow stack frame info (level 0).  */
+  std::optional<shadow_stack_frame_info> curr
+    = get_shadow_stack_frame_info (gdbarch, start_ssp, 0);
+
+  /* Let's position curr on the shadow stack frame at which to start the
+     display.  This could be the innermost frame if the whole shadow stack
+     needs displaying, or if frame_low is 0.  */
+  int frame_num = 0;
+  for (; curr.has_value () && frame_num < frame_low; frame_num++)
+    curr = curr->unwind_prev_shadow_stack_frame_info (*range);
+
+  if (!curr.has_value ())
+    error (_("-shadow-stack-list-frames: Not enough frames on the shadow "
+	     "stack."));
+
+  /* Now let's print the shadow stack frames up to frame_high, or until
+     the bottom of the shadow stack.  */
+  for (; curr.has_value () && (frame_num <= frame_high || frame_high == -1);
+       frame_num++)
+    {
+      QUIT;
+      print_shadow_stack_frame_info (user_frame_print_options, *curr,
+				     LOCATION);
+      curr = curr->unwind_prev_shadow_stack_frame_info (*range);
+    }
+}
diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c
index 48d85e8785c..51f173f66ee 100644
--- a/gdb/mi/mi-cmds.c
+++ b/gdb/mi/mi-cmds.c
@@ -303,6 +303,8 @@ add_builtin_mi_commands ()
   add_mi_cmd_mi ("stack-info-frame", mi_cmd_stack_info_frame);
   add_mi_cmd_mi ("stack-list-arguments", mi_cmd_stack_list_args);
   add_mi_cmd_mi ("stack-list-frames", mi_cmd_stack_list_frames);
+  add_mi_cmd_mi ("shadow-stack-list-frames",
+		 mi_cmd_shadow_stack_list_frames);
   add_mi_cmd_mi ("stack-list-locals", mi_cmd_stack_list_locals);
   add_mi_cmd_mi ("stack-list-variables", mi_cmd_stack_list_variables);
   add_mi_cmd_mi ("stack-select-frame", mi_cmd_stack_select_frame,
diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h
index 108ff645107..9e5d18dbeb4 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 86633cbcb49..a9cbde4ad78 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -276,8 +276,21 @@ do_print_shadow_stack_frame_info
       uiout->text ("#");
       uiout->field_fmt_signed (2, ui_left, "level", frame.level);
 
+      if (uiout->is_mi_like_p ())
+	{
+	  uiout->field_string
+	    ("addr", hex_string_custom (frame.value, element_size * 2),
+	     address_style.style ());
+	}
+
       uiout->field_string ("func", str, metadata_style.style ());
 
+      if (uiout->is_mi_like_p ())
+	{
+	  uiout->field_string
+	    ("arch", gdbarch_bfd_arch_info (frame.arch)->printable_name);
+	}
+
       uiout->text ("\n");
       gdb_flush (gdb_stdout);
       return;
@@ -323,6 +336,12 @@ do_print_shadow_stack_frame_info
 	    print_lib (uiout, lib);
 	}
 
+      if (uiout->is_mi_like_p ())
+	{
+	  uiout->field_string
+	    ("arch", gdbarch_bfd_arch_info (frame.arch)->printable_name);
+	}
+
       uiout->text ("\n");
     }
 
@@ -342,10 +361,9 @@ do_print_shadow_stack_frame_info
   gdb_flush (gdb_stdout);
 }
 
-/* Redirect output to a temporary buffer for the duration of
-   do_print_shadow_stack_frame_info.  */
+/* See shadow-stack.h.  */
 
-static void
+void
 print_shadow_stack_frame_info
   (const frame_print_options &fp_opts,
    const shadow_stack_frame_info &frame,
@@ -372,11 +390,9 @@ ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
   gdb_assert_not_reached ("invalid unwind stop reason.");
 }
 
-/* Read the memory at shadow stack pointer SSP and assign it to
-   RETURN_VALUE.  In case of success, return true.  Otherwise,
-   return false.  */
+/* See shadow-stack.h.  */
 
-static bool
+bool
 read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
 			  CORE_ADDR &return_value)
 {
@@ -435,12 +451,9 @@ get_trailing_outermost_shadow_stack_frame_info
   return trailing;
 }
 
-/* If possible, get shadow stack frame info for the shadow stack pointer
-   SSP and its current frame LEVEL.  Pass FALLBACK_ARCH which can be used
-   as fallback gdbarch in case the gdbarch cannot be extracted from the
-   SAL.  Usually this is the gdbarch of the previous frame.  */
+/* See shadow-stack.h.  */
 
-static std::optional<shadow_stack_frame_info>
+std::optional<shadow_stack_frame_info>
 get_shadow_stack_frame_info
   (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level)
 {
@@ -596,22 +609,35 @@ shadow_stack_frame_info::inside_main_func () const
   return false;
 }
 
-/* Print all elements on the shadow stack or just the innermost COUNT_EXP
-   frames.  */
+/* See shadow-stack.h.  */
 
-void
-backtrace_shadow_command (const frame_print_options &fp_opts,
-			  const char *count_exp, int from_tty)
+CORE_ADDR
+get_validated_shadow_stack_pointer (gdbarch *gdbarch)
 {
   if (!target_has_stack ())
-    error (_("No shadow stack."));
+    {
+      if (!current_uiout->is_mi_like_p ())
+	error (_("No shadow stack."));
+      else
+	error (_("-shadow-stack-list-frames: No shadow stack."));
+    }
 
-  gdbarch *gdbarch = get_current_arch ();
   if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
       || !gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
       || gdbarch_ssp_regnum (gdbarch) == -1)
-    error (_("Printing of the shadow stack backtrace is not supported for"
-	     " the current target."));
+    {
+      if (!current_uiout->is_mi_like_p ())
+	{
+	  error (_("Printing of the shadow stack backtrace is not " \
+		   "supported for the current target."));
+	}
+      else
+	{
+	  error (_("-shadow-stack-list-frames: " \
+		   "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;
@@ -621,10 +647,26 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
 					shadow_stack_enabled);
 
   if (!start_ssp.has_value () || !shadow_stack_enabled)
-    error (_("Shadow stack is not enabled for the current thread."));
+    {
+      if (!current_uiout->is_mi_like_p ())
+	error (_("Shadow stack is not enabled for the current thread."));
+      else
+	{
+	  error (_("-shadow-stack-list-frames: " \
+		   "Shadow stack is not enabled for the current thread."));
+	}
+    }
+
+  return *start_ssp;
+}
 
-  /* Check if START_SSP points to a shadow stack memory range and use
-     the returned range to determine when to stop unwinding.
+/* See shadow-stack.h.  */
+
+std::optional<std::pair<CORE_ADDR, CORE_ADDR>>
+get_non_empty_shadow_stack_range (gdbarch *gdbarch, const CORE_ADDR ssp)
+{
+  /* Check if SSP points to a shadow stack memory range and use the
+     returned range to determine when to stop unwinding.
      Note that a shadow stack memory range can change, due to shadow stack
      switches for instance on x86 for an inter-privilege far call or when
      calling an interrupt/exception handler at a higher privilege level.
@@ -632,26 +674,39 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
      Linux kernel v6.6.  However, shadow stack switches are not supported
      due to missing kernel space support.  We therefore implement this
      command without support for shadow stack switches for now.  */
-  bool is_top_addr_empty_shadow_stack = false;
   std::pair<CORE_ADDR, CORE_ADDR> range;
-  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
-						     &range))
+  if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, ssp, &range))
     {
       /* For x86, if the current shadow stack pointer does not point to
 	 shadow stack memory but is valid, the shadow stack is empty.  */
-      is_top_addr_empty_shadow_stack = true;
+      return {};
     }
 
-  if (!is_top_addr_empty_shadow_stack)
+  if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
+      && gdbarch_top_addr_empty_shadow_stack (gdbarch, ssp, range))
     {
       /* For ARM's Guarded Control Stack, the shadow stack can be empty
 	 even though START_SSP points to shadow stack memory range.  */
-      is_top_addr_empty_shadow_stack
-	= gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
-	  && gdbarch_top_addr_empty_shadow_stack (gdbarch, *start_ssp, range);
+      return {};
     }
 
-  if (is_top_addr_empty_shadow_stack)
+  return {range};
+}
+
+/* Print all elements on the shadow stack or just the innermost COUNT_EXP
+   frames.  */
+
+void
+backtrace_shadow_command (const frame_print_options &fp_opts,
+			  const char *count_exp, int from_tty)
+{
+  gdbarch *gdbarch = get_current_arch ();
+  const CORE_ADDR start_ssp
+    = get_validated_shadow_stack_pointer (gdbarch);
+
+  std::optional<std::pair<CORE_ADDR, CORE_ADDR>> range
+    = get_non_empty_shadow_stack_range (gdbarch, start_ssp);
+  if (!range.has_value ())
     {
       gdb_printf (_("The shadow stack is empty.\n"));
       return;
@@ -660,7 +715,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
   /* Extract the first shadow stack frame info (level 0).  */
   ssp_unwind_stop_reason reason = ssp_unwind_stop_reason::no_error;
   std::optional<shadow_stack_frame_info> current
-    = get_shadow_stack_frame_info (gdbarch, *start_ssp, 0);
+    = get_shadow_stack_frame_info (gdbarch, start_ssp, 0);
 
   if (!current.has_value ())
     reason = ssp_unwind_stop_reason::memory_read_error;
@@ -676,7 +731,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
       if (count < 0)
 	{
 	  trailing = get_trailing_outermost_shadow_stack_frame_info
-		       (range, std::abs (count), *current);
+		       (*range, std::abs (count), *current);
 
 	  if (!trailing.has_value ())
 	    reason = current->unwind_stop_reason;
@@ -698,7 +753,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
       print_shadow_stack_frame_info (fp_opts, *current, LOCATION);
 
       trailing = current;
-      current = current->unwind_prev_shadow_stack_frame_info (range);
+      current = current->unwind_prev_shadow_stack_frame_info (*range);
     }
 
   /* If we've stopped before the end, mention that.  */
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index d4b0d53acf3..8cdc389846a 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -139,4 +139,47 @@ class shadow_stack_frame_info
   bool inside_main_func () const;
 };
 
+/* Read the memory at shadow stack pointer SSP and assign it to
+   RETURN_VALUE.  In case of success, return true.  Otherwise, return
+   false.  */
+
+bool read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
+			       CORE_ADDR &return_value);
+
+/* Print information of shadow stack frame info FRAME.  The output is
+   formatted according to PRINT_WHAT.  For the meaning of PRINT_WHAT, see
+   enum print_what comments in frame.h.  Note that PRINT_WHAT is
+   overridden, if PRINT_OPTIONS.print_frame_info != print_frame_info_auto.
+   Redirect output to a temporary buffer for the duration of
+   do_print_shadow_stack_frame_info.  */
+
+void print_shadow_stack_frame_info
+  (const frame_print_options &fp_opts,
+   const shadow_stack_frame_info &frame,
+   print_what print_what);
+
+/* Check if shadow stacks are supported and enabled.  If it is, return the
+   validated shadow stack pointer.  If not, throw an appropriate error
+   message.
+   This helper is specific to printing the shadow stack backtrace and must
+   only be called from the CLI command "bt -shadow" or its MI equivalent
+   "-shadow-stack-list-frames" since the error messages are specific to
+   these commands.  */
+
+CORE_ADDR get_validated_shadow_stack_pointer (gdbarch *gdbarch);
+
+/* If the shadow stack pointed to by SSP is empty, return an empty optional.
+   Otherwise return the shadow stack memory range.  */
+
+std::optional<std::pair<CORE_ADDR, CORE_ADDR>>
+  get_non_empty_shadow_stack_range (gdbarch *gdbarch, const CORE_ADDR ssp);
+
+/* If possible, get shadow stack frame info for the shadow stack pointer
+   SSP and its current frame LEVEL.  Pass FALLBACK_ARCH which can be used
+   as fallback gdbarch in case the gdbarch cannot be extracted from the
+   SAL.  Usually this is the gdbarch of the previous frame.  */
+
+std::optional<shadow_stack_frame_info> get_shadow_stack_frame_info
+  (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level);
+
 #endif /* GDB_SHADOW_STACK_H */
diff --git a/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
new file mode 100644
index 00000000000..c9e9c80a76b
--- /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-signal
+
+    # Test shadow-stack-list-frames for shadow stack element which is no
+    # return address.
+    if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+	{debug additional_flags="-fcf-protection=return"}] } {
+	return
+    }
+
+    if { [mi_clean_restart $testfile] } {
+	return
+    }
+
+    mi_runto_main
+    mi_send_resuming_command "exec-continue" "continue till signal"
+
+    set r_signal "reason=\"signal-received\",signal-name=\"SIGUSR1\",signal-meaning=\"User defined signal 1\""
+    gdb_expect {
+	-re ".*stopped,${r_signal}.*$mi_gdb_prompt" {
+	    pass "Wait for user interrupt"
+	}
+	timeout {
+	    fail "Wait for user interrupt (timeout)"
+	    return
+	}
+    }
+
+    mi_gdb_test "break handler" \
+	{(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done}
+
+    mi_execute_to "exec-continue" "breakpoint-hit" "handler" ".*" ".*" ".*" \
+	{"" "disp=\"keep\""} "continue to handler"
+
+    # We only test the frame belonging to the shadow stack element which
+    # is not a return address.  This frame is triggered by the signal
+    # exception.
+    set any "\[^\"\]+"
+    mi_gdb_test "231-shadow-stack-list-frames 1 1" \
+	"231\\^done,shadow-stack=\\\[shadow-stack-frame=\{level=\"1\",addr=\"$hex\",func=\"<sigframe token>\",arch=\"$any\"\}\\\]" \
+	"test shadow-stack-list-frames"
+
+    mi_gdb_exit
+}
diff --git a/gdb/testsuite/gdb.mi/mi-shadow-stack.exp b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
new file mode 100644
index 00000000000..ac045d6f3a2
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp
@@ -0,0 +1,93 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test the mi command -shadow-stack-list-frames.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+require allow_ssp_tests
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+    append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+    set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack.c"
+    set testfile mi-shadow-stack
+
+    if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+	  {debug additional_flags="-fcf-protection=return"}] } {
+	return
+    }
+
+    if { [mi_clean_restart $testfile] } {
+	return
+    }
+
+    mi_runto_main
+
+    mi_gdb_test "break call2" \
+	{(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done}
+
+    mi_execute_to "exec-continue" "breakpoint-hit" "call2" ".*" ".*" ".*" \
+	{"" "disp=\"keep\""} "continue to call2"
+
+    set any "\[^\"\]+"
+    set any_remaining_frame_attr "\[^\r\n]+"
+
+    # It's enough to test the first 3 frames.  For frame 3 we just test that it
+    # exists as other attributes might depend on the environment.
+    set frame_start "shadow-stack-frame=\{level="
+    set frame1 "$frame_start\"0\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}"
+    set frame2 "$frame_start\"1\",addr=\"$hex\",func=\"main\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}"
+    set frame3 "$frame_start\"2\",addr=\"$hex\"$any_remaining_frame_attr\}"
+    mi_gdb_test "231-shadow-stack-list-frames" \
+	"231\\^done,shadow-stack=\\\[$frame1.*$frame2\\\]" \
+	"test shadow-stack-list-frames"
+
+    mi_gdb_test "set backtrace past-main on" \
+	".*=cmd-param-changed,param=\"backtrace past-main\",value=\"on\".*\\^done"
+    mi_gdb_test "231-shadow-stack-list-frames" \
+	"231\\^done,shadow-stack=\\\[$frame1.*$frame2.*$frame3.*\\\]" \
+	"test shadow-stack-list-frames past main"
+
+    # Test low-frame/high-frame
+    mi_gdb_test "231-shadow-stack-list-frames 0 1" \
+	"231\\^done,shadow-stack=\\\[$frame1.*$frame2\\\]" \
+	"test shadow-stack-list-frames low/high-frames"
+
+    # Test inferior function calls.
+    set inside_infcall_str "The program being debugged stopped while in a function called from GDB"
+    mi_gdb_test "call (int) call2()" \
+	".*Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" \
+	"call (int) call2()"
+
+    set dummy_frame1 "$frame_start\"0\",addr=\"$hex\",func=\"<function called from gdb>\",arch=\"$any\"\}"
+    set frame1 "$frame_start\"1\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}"
+    mi_gdb_test "231-shadow-stack-list-frames" \
+	"231\\^done,shadow-stack=\\\[$dummy_frame1.*$frame1.*" \
+	"test shadow-stack-list-frames inside inferior call"
+
+    mi_gdb_test "call (int) call2()" \
+	".*Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" \
+	"Execute an inferior call inside an inferior call."
+
+    set dummy_frame2 "$frame_start\"1\",addr=\"$hex\",func=\"<function called from gdb>\",arch=\"$any\"\}"
+    set frame2 "$frame_start\"2\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}"
+    mi_gdb_test "231-shadow-stack-list-frames" \
+	"231\\^done,shadow-stack=\\\[$dummy_frame1.*$dummy_frame2.*$frame2.*" \
+	"test shadow-stack-list-frames inside nested inferior call"
+
+    mi_gdb_exit
+}
-- 
2.53.0

________________________________________
Intel Deutschland GmbH 

Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany 

Tel: +49 (89) 99143-0 

www.intel.de 

Managing Directors: Candice Moore, Jeffrey Schneiderman, Ramachandran Sitaraman

Chairperson of the Supervisory Board: Sonja Pierer

Registered Seat: Munich Commercial Register B: Amtsgericht Munich HRB 186928

This e-mail and any attachments may contain confidential material for
the sole use of the intended recipient(s). Any review or distribution
by others is strictly prohibited. If you are not the intended
recipient, please contact the sender and delete all copies.


  parent reply	other threads:[~2026-09-24  8:38 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-24  8:32 [PATCH v5 00/13] Add new command to print the shadow stack backtrace Christina Joos
2026-09-24  8:32 ` [PATCH v5 01/13] gdb: Generalize handling of the shadow stack pointer Christina Joos
2026-09-24 21:03   ` Thiago Jung Bauermann
2026-09-24  8:32 ` [PATCH v5 02/13] aarch64: Implement gdbarch function top_addr_empty_shadow_stack Christina Joos
2026-09-24 21:04   ` Thiago Jung Bauermann
2026-09-24  8:33 ` [PATCH v5 03/13] gdb: Add get_main_func_start_pc to refactor frame.c:inside_main_func Christina Joos
2026-09-24  8:33 ` [PATCH v5 04/13] gdb: Refactor 'stack.c:print_frame' Christina Joos
2026-09-24  8:33 ` [PATCH v5 05/13] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Joos
2026-09-24  8:33 ` [PATCH v5 06/13] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Joos
2026-09-24  8:33 ` [PATCH v5 07/13] gdb: Refactor 'stack.c:print_frame_info' Christina Joos
2026-09-24  8:33 ` [PATCH v5 08/13] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Joos
2026-09-24  9:37   ` Eli Zaretskii
2026-09-24  8:33 ` [PATCH v5 09/13] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Joos
2026-09-24  9:29   ` Eli Zaretskii
2026-09-24 13:10     ` Joos, Christina
2026-09-24  8:33 ` [PATCH v5 10/13] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Joos
2026-09-24  8:33 ` [PATCH v5 11/13] gdb: Enable inferior calls in the shadow stack backtrace Christina Joos
2026-09-24  8:33 ` [PATCH v5 12/13] gdb: Enable signal trampolines " Christina Joos
2026-09-24  8:33 ` Christina Joos [this message]
2026-09-24  9:39   ` [PATCH v5 13/13] gdb, mi: Add -shadow-stack-list-frames command Eli Zaretskii

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=20260924083311.1961530-14-christina.joos@intel.com \
    --to=christina.joos@intel.com \
    --cc=gdb-patches@sourceware.org \
    --cc=luis.machado.foss@gmail.com \
    --cc=thiago.bauermann@linaro.org \
    --cc=tom@tromey.com \
    /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