Christina Schimpe <christina.schimpe@intel.com> writes:
> Add the mi command for the command "backtrace -shadow".
> Similar to the mi interface for the ordinary backtrace command,
> support low-frame and high-frame as command line parameters.
>
> Example print of a full shadow stack backtrace:
> ~~~
> (gdb)
> -shadow-stack-list-frames
> ^done,shadow-stack=[
> shadow-stack-frame={level="0",addr="0x00007ffff7c3fe70",
> func="__libc_start_call_main",file="../sysdeps/nptl/libc_start_call_main.h",
> fullname="/usr/[...]/sysdeps/nptl/libc_start_call_main.h",
> line="58",arch="i386:x86-64"},
> shadow-stack-frame={level="1",addr="0x00007ffff7c3ff20",
> func="__libc_start_main_impl",file="../csu/libc-start.c",
> fullname="/usr/[...]/csu/libc-start.c",
> line="128",arch="i386:x86-64"},
> shadow-stack-frame={level="2",addr="0x0000000000401075",
> func="_start",arch="i386:x86-64"}]
> ~~~
>
> Example print of a shadow stack backtrace using low- and high-frame:
> ~~~
> (gdb)
> -shadow-stack-list-frames 0 1
> ^done,shadow-stack=[
> shadow-stack-frame={level="0",addr="0x00007ffff7c3fe70",
> func="__libc_start_call_main",file="../sysdeps/nptl/libc_start_call_main.h",
> fullname="/usr/[...]/sysdeps/nptl/libc_start_call_main.h",
> line="58",arch="i386:x86-64"},
> shadow-stack-frame={level="1",addr="0x00007ffff7c3ff20",
> func="__libc_start_main_impl",file="../csu/libc-start.c",
> fullname="/usr/[...]/csu/libc-start.c",
> line="128",arch="i386:x86-64"}]
> ~~~
> ---
> gdb/NEWS | 8 ++
> gdb/doc/gdb.texinfo | 51 ++++++++
> gdb/mi/mi-cmd-stack.c | 100 +++++++++++++++
> gdb/mi/mi-cmds.c | 2 +
> gdb/mi/mi-cmds.h | 1 +
> gdb/shadow-stack.c | 121 ++++++++++++------
> gdb/shadow-stack.h | 40 ++++++
> .../gdb.mi/mi-shadow-stack-signal.exp | 69 ++++++++++
> gdb/testsuite/gdb.mi/mi-shadow-stack.exp | 93 ++++++++++++++
> 9 files changed, 449 insertions(+), 36 deletions(-)
> create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
> create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack.exp
I don't have much experience with MI, so I'll only comment on the
translation strings. Though to be honest in all this time AFAICT GDB
hasn't ever been translated so I wonder how useful it is to pay
attention to this.
> +/* Parse arguments of -shadow-stack-list-frames command and set FRAME_LOW
> + and FRAME_HIGH accordingly. Throw an error in case the arguments are
> + invalid. */
> +static void
> +mi_cmd_shadow_stack_list_frames_parse_args (const char *const *argv,
> + int argc, int &frame_low,
> + int &frame_high)
> +{
> + const std::string mi_cmd_name = "-shadow-stack-list-frames";
> + /* There should either be low - high range, or no arguments. */
> + if ((argc != 0) && (argc != 2))
> + error (_("%s: Usage: [FRAME_LOW FRAME_HIGH]"), mi_cmd_name.c_str ());
> +
> + /* If there is a range, set it. */
> + if (argc == 2)
> + {
> + frame_low = atoi (argv[0]);
> + frame_high = atoi (argv[1]);
> + std::string err_str;
> + if (frame_low < 0)
> + {
> + err_str = "``" + std::to_string (frame_low) + "''";
> + if (frame_high < 0)
> + err_str += " and ``" + std::to_string (frame_high) + "''";
> + }
> + else if (frame_high < 0)
> + err_str = "``" + std::to_string (frame_high) + "''";
> +
> + if (!err_str.empty ())
> + {
> + err_str = mi_cmd_name + ": Invalid option " + err_str + ".";
> + error (_("%s"), err_str.c_str ());
Passing "%s" as a string to be translated doesn't work. gettext extracts
the translatable strings by scanning the source code at build time, it's
not done at runtime.
You can see this if you run this in the GDB build directory:
$ make -C gdb gdb.pot
Then you'll see in $build_dir/gdb/po/gdb.pot:
#: compile/compile.c:112 cp-namespace.c:528 dwarf2/read.c:1021
#: dwarf2/read.c:1030 mi/mi-cmd-stack.c:802 shadow-stack.c:632
#: shadow-stack.c:649 utils.c:657 read.c:1021 read.c:1030 mi-cmd-stack.c:802
#: compile.c:112
#, possible-c-format
msgid "%s"
msgstr ""
Which isn't very translatable. The only string containing "Invalid
option" in gdb.pot is from mi-cmd-var.c, unrelated to this code.
> + }
> + }
> + else
> + {
> + /* No arguments, print the whole shadow stack backtrace. */
> + frame_low = -1;
> + frame_high = -1;
> + }
> +}
⋮
> +CORE_ADDR
> +get_validated_shadow_stack_pointer (gdbarch *gdbarch)
> {
> if (!target_has_stack ())
> - error (_("No shadow stack."));
> + {
> + if (!current_uiout->is_mi_like_p ())
> + error (_("No shadow stack."));
> + else
> + error (_("-shadow-stack-list-frames: No shadow stack."));
> + }
>
> - gdbarch *gdbarch = get_current_arch ();
> if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
> || !gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
> || gdbarch_ssp_regnum (gdbarch) == -1)
> - error (_("Printing of the shadow stack backtrace is not supported for"
> - " the current target."));
> + {
> + std::string err_str = "Printing of the shadow stack backtrace";
> + err_str += " is not supported for the current target.";
> + if (!current_uiout->is_mi_like_p ())
> + error (_("%s"), err_str.c_str());
> + else
> + error (_("-shadow-stack-list-frames: %s"), err_str.c_str());
Same problem in the two _() calls above, and the two below.
> + }
>
> regcache *regcache = get_thread_regcache (inferior_thread ());
> bool shadow_stack_enabled = false;
> @@ -621,10 +642,25 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
> shadow_stack_enabled);
>
> if (!start_ssp.has_value () || !shadow_stack_enabled)
> - error (_("Shadow stack is not enabled for the current thread."));
> + {
> + std::string err_str
> + = "Shadow stack is not enabled for the current thread.";
> + if (!current_uiout->is_mi_like_p ())
> + error (_("%s"), err_str.c_str());
> + else
> + error (_("-shadow-stack-list-frames: %s"), err_str.c_str());
> + }
>
> - /* Check if START_SSP points to a shadow stack memory range and use
> - the returned range to determine when to stop unwinding.
> + return *start_ssp;
> +}
@@ -333,6 +333,14 @@ qExecAndArgs
AIX version is now AIX 7.2 TL5. GDB will only support
DWARF debugging in AIX, going forward.
+* New MI commands
+
+-shadow-stack-list-frames
+ Added new MI command which is equivalent to the CLI command
+ 'backtrace -shadow' but supports 'low-frame' and 'high-frame' as
+ command line parameters. The parameters are used to print shadow
+ stack frames between between certain levels on the shadow stack only.
+
*** Changes in GDB 17
* Debugging Linux programs that use x86-64 or x86-64 with 32-bit pointer
@@ -35532,6 +35532,57 @@ Show a single frame:
(gdb)
@end smallexample
+@anchor{-shadow-stack-list-frames}
+@findex -shadow-stack-list-frames
+@subheading The @code{-shadow-stack-list-frames} Command
+
+@subsubheading Synopsis
+
+@smallexample
+ -shadow-stack-list-frames [ @var{low-frame} @var{high-frame} ]
+@end smallexample
+
+List the shadow stack frames currently on the shadow stack. In case the
+element on the shadow stack is a return address, @value{GDBN} prints the
+same fields as in @code{-stack-list-frames} with the return address on
+the shadow stack as @var{addr}.
+If the element on the shadow stack is not a return address, @value{GDBN}
+only prints @var{level}, @var{addr} and @var{arch}.
+
+If invoked without arguments, this command prints a backtrace for the
+whole shadow stack. Like the @code{-stack-list-frames} command, if given
+two integer arguments, it shows the frames whose levels are between the
+two arguments (inclusive).
+
+@subsubheading @value{GDBN} Command
+
+The corresponding @value{GDBN} command is @samp{backtrace -shadow}.
+
+@subsubheading Example
+
+Show shadow stack frames between @var{low-frame} and @var{high-frame}:
+
+@smallexample
+(gdb)
+-shadow-stack-list-frames 0 2
+^done,shadow-stack=[
+shadow-stack-frame=@{level="0",addr="0x00007ffff7c45330",
+ func="<signal handler called>",arch="i386:x86-64"@},
+shadow-stack-frame=@{level="1",addr="0x80007ffff7bfffd8",
+ func="<sigframe token>",arch="i386:x86-64"@},
+shadow-stack-frame=@{level="2",addr="0x00007ffff7c4527e",
+ func="__GI_raise",file="[...]/raise.c",
+ fullname="./[...]/posix/raise.c",
+ line="26",arch="i386:x86-64"@}]
+(gdb)
+@end smallexample
+
+Note that for frame 0 and 1, @value{GDBN} printed only @var{level},
+@var{addr}, @var{func} and @var{arch}. For frame 0, @var{func} is
+@code{"<signal handler called>"} similar to the @code{-stack-list-frames}
+command. For frame 1 @value{GDBN} prints @code{"<sigframe token>"} for
+@var{func}. This is due to an element on the shadow stack which is not a
+return address.
@findex -stack-list-locals
@anchor{-stack-list-locals}
@@ -32,6 +32,9 @@
#include "mi-parse.h"
#include <optional>
#include "inferior.h"
+#include "shadow-stack.h"
+#include "gdbarch.h"
+#include "arch-utils.h"
enum what_to_list { locals, arguments, all };
@@ -764,3 +767,100 @@ mi_cmd_stack_info_frame (const char *command, const char *const *argv,
print_frame_info (user_frame_print_options,
get_selected_frame (), 1, LOC_AND_ADDRESS, 0, 1);
}
+
+/* Parse arguments of -shadow-stack-list-frames command and set FRAME_LOW
+ and FRAME_HIGH accordingly. Throw an error in case the arguments are
+ invalid. */
+static void
+mi_cmd_shadow_stack_list_frames_parse_args (const char *const *argv,
+ int argc, int &frame_low,
+ int &frame_high)
+{
+ const std::string mi_cmd_name = "-shadow-stack-list-frames";
+ /* There should either be low - high range, or no arguments. */
+ if ((argc != 0) && (argc != 2))
+ error (_("%s: Usage: [FRAME_LOW FRAME_HIGH]"), mi_cmd_name.c_str ());
+
+ /* If there is a range, set it. */
+ if (argc == 2)
+ {
+ frame_low = atoi (argv[0]);
+ frame_high = atoi (argv[1]);
+ std::string err_str;
+ if (frame_low < 0)
+ {
+ err_str = "``" + std::to_string (frame_low) + "''";
+ if (frame_high < 0)
+ err_str += " and ``" + std::to_string (frame_high) + "''";
+ }
+ else if (frame_high < 0)
+ err_str = "``" + std::to_string (frame_high) + "''";
+
+ if (!err_str.empty ())
+ {
+ err_str = mi_cmd_name + ": Invalid option " + err_str + ".";
+ error (_("%s"), err_str.c_str ());
+ }
+ }
+ else
+ {
+ /* No arguments, print the whole shadow stack backtrace. */
+ frame_low = -1;
+ frame_high = -1;
+ }
+}
+
+/* Print a list of the shadow stack frames. Args can be none, in which
+ case we want to print the whole shadow stack backtrace, or a pair of
+ numbers specifying the frame numbers at which to start and stop the
+ display. If the two numbers are equal, a single frame will be
+ displayed. */
+
+void
+mi_cmd_shadow_stack_list_frames (const char *command,
+ const char *const *argv,
+ int argc)
+{
+ int frame_low;
+ int frame_high;
+
+ mi_cmd_shadow_stack_list_frames_parse_args (argv, argc, frame_low,
+ frame_high);
+
+ gdbarch *gdbarch = get_current_arch ();
+ const CORE_ADDR start_ssp
+ = get_validated_shadow_stack_pointer (gdbarch);
+
+ ui_out_emit_list list_emitter (current_uiout, "shadow-stack");
+
+ std::optional<std::pair<CORE_ADDR, CORE_ADDR>> range
+ = get_non_empty_shadow_stack_range (gdbarch, start_ssp);
+ if (!range.has_value ())
+ return;
+
+ /* Extract the first shadow stack frame info (level 0). */
+ std::optional<shadow_stack_frame_info> curr
+ = get_shadow_stack_frame_info (gdbarch, start_ssp, 0);
+
+ /* Let's position curr on the shadow stack frame at which to start the
+ display. This could be the innermost frame if the whole shadow stack
+ needs displaying, or if frame_low is 0. */
+ int frame_num = 0;
+ for (; curr.has_value () && frame_num < frame_low; frame_num++)
+ curr = curr->unwind_prev_shadow_stack_frame_info (*range);
+
+ if (!curr.has_value ())
+ error (_("-shadow-stack-list-frames: Not enough frames on the shadow "
+ "stack."));
+
+ /* Now let's print the shadow stack frames up to frame_high, or until
+ the bottom of the shadow stack. */
+ for (; curr.has_value () && (frame_num <= frame_high || frame_high == -1);
+ frame_num++)
+ {
+ QUIT;
+ print_shadow_stack_frame_info (user_frame_print_options, *curr,
+ LOCATION);
+ curr = curr->unwind_prev_shadow_stack_frame_info (*range);
+ }
+}
@@ -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,
@@ -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;
@@ -276,8 +276,21 @@ do_print_shadow_stack_frame_info
uiout->text ("#");
uiout->field_fmt_signed (2, ui_left, "level", frame.level);
+ if (uiout->is_mi_like_p ())
+ {
+ uiout->field_string
+ ("addr", hex_string_custom (frame.value, element_size * 2),
+ address_style.style ());
+ }
+
uiout->field_string ("func", str, metadata_style.style ());
+ if (uiout->is_mi_like_p ())
+ {
+ uiout->field_string
+ ("arch", gdbarch_bfd_arch_info (frame.arch)->printable_name);
+ }
+
uiout->text ("\n");
gdb_flush (gdb_stdout);
return;
@@ -323,6 +336,12 @@ do_print_shadow_stack_frame_info
print_lib (uiout, lib);
}
+ if (uiout->is_mi_like_p ())
+ {
+ uiout->field_string
+ ("arch", gdbarch_bfd_arch_info (frame.arch)->printable_name);
+ }
+
uiout->text ("\n");
}
@@ -342,10 +361,9 @@ do_print_shadow_stack_frame_info
gdb_flush (gdb_stdout);
}
-/* Redirect output to a temporary buffer for the duration of
- do_print_shadow_stack_frame_info. */
+/* See shadow-stack.h. */
-static void
+void
print_shadow_stack_frame_info
(const frame_print_options &fp_opts,
const shadow_stack_frame_info &frame,
@@ -372,11 +390,9 @@ ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason)
gdb_assert_not_reached ("invalid unwind stop reason.");
}
-/* Read the memory at shadow stack pointer SSP and assign it to
- RETURN_VALUE. In case of success, return true. Otherwise,
- return false. */
+/* See shadow-stack.h. */
-static bool
+bool
read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
CORE_ADDR &return_value)
{
@@ -435,12 +451,9 @@ get_trailing_outermost_shadow_stack_frame_info
return trailing;
}
-/* If possible, get shadow stack frame info for the shadow stack pointer
- SSP and its current frame LEVEL. Pass FALLBACK_ARCH which can be used
- as fallback gdbarch in case the gdbarch cannot be extracted from the
- SAL. Usually this is the gdbarch of the previous frame. */
+/* See shadow-stack.h. */
-static std::optional<shadow_stack_frame_info>
+std::optional<shadow_stack_frame_info>
get_shadow_stack_frame_info
(gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level)
{
@@ -596,22 +609,30 @@ shadow_stack_frame_info::inside_main_func () const
return false;
}
-/* Print all elements on the shadow stack or just the innermost COUNT_EXP
- frames. */
+/* See shadow-stack.h. */
-void
-backtrace_shadow_command (const frame_print_options &fp_opts,
- const char *count_exp, int from_tty)
+CORE_ADDR
+get_validated_shadow_stack_pointer (gdbarch *gdbarch)
{
if (!target_has_stack ())
- error (_("No shadow stack."));
+ {
+ if (!current_uiout->is_mi_like_p ())
+ error (_("No shadow stack."));
+ else
+ error (_("-shadow-stack-list-frames: No shadow stack."));
+ }
- gdbarch *gdbarch = get_current_arch ();
if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)
|| !gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
|| gdbarch_ssp_regnum (gdbarch) == -1)
- error (_("Printing of the shadow stack backtrace is not supported for"
- " the current target."));
+ {
+ std::string err_str = "Printing of the shadow stack backtrace";
+ err_str += " is not supported for the current target.";
+ if (!current_uiout->is_mi_like_p ())
+ error (_("%s"), err_str.c_str());
+ else
+ error (_("-shadow-stack-list-frames: %s"), err_str.c_str());
+ }
regcache *regcache = get_thread_regcache (inferior_thread ());
bool shadow_stack_enabled = false;
@@ -621,10 +642,25 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
shadow_stack_enabled);
if (!start_ssp.has_value () || !shadow_stack_enabled)
- error (_("Shadow stack is not enabled for the current thread."));
+ {
+ std::string err_str
+ = "Shadow stack is not enabled for the current thread.";
+ if (!current_uiout->is_mi_like_p ())
+ error (_("%s"), err_str.c_str());
+ else
+ error (_("-shadow-stack-list-frames: %s"), err_str.c_str());
+ }
- /* Check if START_SSP points to a shadow stack memory range and use
- the returned range to determine when to stop unwinding.
+ return *start_ssp;
+}
+
+/* See shadow-stack.h. */
+
+std::optional<std::pair<CORE_ADDR, CORE_ADDR>>
+get_non_empty_shadow_stack_range (gdbarch *gdbarch, const CORE_ADDR ssp)
+{
+ /* Check if SSP points to a shadow stack memory range and use the
+ returned range to determine when to stop unwinding.
Note that a shadow stack memory range can change, due to shadow stack
switches for instance on x86 for an inter-privilege far call or when
calling an interrupt/exception handler at a higher privilege level.
@@ -632,26 +668,39 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
Linux kernel v6.6. However, shadow stack switches are not supported
due to missing kernel space support. We therefore implement this
command without support for shadow stack switches for now. */
- bool is_top_addr_empty_shadow_stack = false;
std::pair<CORE_ADDR, CORE_ADDR> range;
- if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp,
- &range))
+ if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, ssp, &range))
{
/* For x86, if the current shadow stack pointer does not point to
shadow stack memory but is valid, the shadow stack is empty. */
- is_top_addr_empty_shadow_stack = true;
+ return {};
}
- if (!is_top_addr_empty_shadow_stack)
+ if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
+ && gdbarch_top_addr_empty_shadow_stack (gdbarch, ssp, range))
{
/* For ARM's Guarded Control Stack, the shadow stack can be empty
even though START_SSP points to shadow stack memory range. */
- is_top_addr_empty_shadow_stack
- = gdbarch_top_addr_empty_shadow_stack_p (gdbarch)
- && gdbarch_top_addr_empty_shadow_stack (gdbarch, *start_ssp, range);
+ return {};
}
- if (is_top_addr_empty_shadow_stack)
+ return {range};
+}
+
+/* Print all elements on the shadow stack or just the innermost COUNT_EXP
+ frames. */
+
+void
+backtrace_shadow_command (const frame_print_options &fp_opts,
+ const char *count_exp, int from_tty)
+{
+ gdbarch *gdbarch = get_current_arch ();
+ const CORE_ADDR start_ssp
+ = get_validated_shadow_stack_pointer (gdbarch);
+
+ std::optional<std::pair<CORE_ADDR, CORE_ADDR>> range
+ = get_non_empty_shadow_stack_range (gdbarch, start_ssp);
+ if (!range.has_value ())
{
gdb_printf (_("The shadow stack is empty.\n"));
return;
@@ -660,7 +709,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
/* Extract the first shadow stack frame info (level 0). */
ssp_unwind_stop_reason reason = ssp_unwind_stop_reason::no_error;
std::optional<shadow_stack_frame_info> current
- = get_shadow_stack_frame_info (gdbarch, *start_ssp, 0);
+ = get_shadow_stack_frame_info (gdbarch, start_ssp, 0);
if (!current.has_value ())
reason = ssp_unwind_stop_reason::memory_read_error;
@@ -676,7 +725,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
if (count < 0)
{
trailing = get_trailing_outermost_shadow_stack_frame_info
- (range, std::abs (count), *current);
+ (*range, std::abs (count), *current);
if (!trailing.has_value ())
reason = current->unwind_stop_reason;
@@ -698,7 +747,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts,
print_shadow_stack_frame_info (fp_opts, *current, LOCATION);
trailing = current;
- current = current->unwind_prev_shadow_stack_frame_info (range);
+ current = current->unwind_prev_shadow_stack_frame_info (*range);
}
/* If we've stopped before the end, mention that. */
@@ -139,4 +139,44 @@ class shadow_stack_frame_info
bool inside_main_func () const;
};
+/* Read the memory at shadow stack pointer SSP and assign it to
+ RETURN_VALUE. In case of success, return true. Otherwise, return
+ false. */
+
+bool read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
+ CORE_ADDR &return_value);
+
+/* Print information of shadow stack frame info FRAME. The output is
+ formatted according to PRINT_WHAT. For the meaning of PRINT_WHAT, see
+ enum print_what comments in frame.h. Note that PRINT_WHAT is
+ overridden, if PRINT_OPTIONS.print_frame_info != print_frame_info_auto.
+ Redirect output to a temporary buffer for the duration of
+ do_print_shadow_stack_frame_info. */
+
+void print_shadow_stack_frame_info
+ (const frame_print_options &fp_opts,
+ const shadow_stack_frame_info &frame,
+ print_what print_what);
+
+/* Check if shadow stacks are supported and enabled. If it is, return the
+ validated shadow stack pointer. If not, throw an appropriate error
+ message, either for the CLI command "bt -shadow" or for the MI equivalent
+ -shadow-stack-list-frames. */
+
+CORE_ADDR get_validated_shadow_stack_pointer (gdbarch *gdbarch);
+
+/* If the shadow stack pointed to by SSP is empty, return an empty optional.
+ Otherwise return the shadow stack memory range. */
+
+std::optional<std::pair<CORE_ADDR, CORE_ADDR>>
+ get_non_empty_shadow_stack_range (gdbarch *gdbarch, const CORE_ADDR ssp);
+
+/* If possible, get shadow stack frame info for the shadow stack pointer
+ SSP and its current frame LEVEL. Pass FALLBACK_ARCH which can be used
+ as fallback gdbarch in case the gdbarch cannot be extracted from the
+ SAL. Usually this is the gdbarch of the previous frame. */
+
+std::optional<shadow_stack_frame_info> get_shadow_stack_frame_info
+ (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level);
+
#endif /* GDB_SHADOW_STACK_H */
new file mode 100644
@@ -0,0 +1,69 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test the mi command -shadow-stack-list-frames for signal handling on linux.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+require allow_ssp_tests {istarget "*-*-linux*"}
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+ append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+ set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack-signal.c"
+ set testfile mi-shadow-stack
+
+ # Test shadow-stack-list-frames for shadow stack element which is no
+ # return address.
+ if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+ {debug additional_flags="-fcf-protection=return"}] } {
+ return
+ }
+
+ if { [mi_clean_restart $testfile] } {
+ return
+ }
+
+ mi_runto_main
+ mi_send_resuming_command "exec-continue" "continue till signal"
+
+ set r_signal "reason=\"signal-received\",signal-name=\"SIGUSR1\",signal-meaning=\"User defined signal 1\""
+ gdb_expect {
+ -re ".*stopped,${r_signal}.*$mi_gdb_prompt" {
+ pass "Wait for user interrupt"
+ }
+ timeout {
+ fail "Wait for user interrupt (timeout)"
+ return
+ }
+ }
+
+ mi_gdb_test "break handler" \
+ {(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done}
+
+ mi_execute_to "exec-continue" "breakpoint-hit" "handler" ".*" ".*" ".*" \
+ {"" "disp=\"keep\""} "continue to handler"
+
+ # We only test the frame belonging to the shadow stack element which
+ # is not a return address. This frame is triggered by the signal
+ # exception.
+ set any "\[^\"\]+"
+ mi_gdb_test "231-shadow-stack-list-frames 1 1" \
+ "231\\^done,shadow-stack=\\\[shadow-stack-frame=\{level=\"1\",addr=\"$hex\",func=\"<sigframe token>\",arch=\"$any\"\}\\\]" \
+ "test shadow-stack-list-frames"
+
+ mi_gdb_exit
+}
new file mode 100644
@@ -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
+}