From: Christina Schimpe <christina.schimpe@intel.com>
To: gdb-patches@sourceware.org
Cc: tom@tromey.com, thiago.bauermann@linaro.org
Subject: [PATCH v4 13/13] gdb, mi: Add -shadow-stack-list-frames command
Date: Wed, 8 Jul 2026 14:36:39 +0000 [thread overview]
Message-ID: <20260708143639.2214689-14-christina.schimpe@intel.com> (raw)
In-Reply-To: <20260708143639.2214689-1-christina.schimpe@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 | 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
diff --git a/gdb/NEWS b/gdb/NEWS
index fa06d21ddac..8d45ec373f2 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -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
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 665576c4fc4..0f48709fc44 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -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}
diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c
index 280a08d121e..e6948838da4 100644
--- a/gdb/mi/mi-cmd-stack.c
+++ b/gdb/mi/mi-cmd-stack.c
@@ -32,6 +32,9 @@
#include "mi-parse.h"
#include <optional>
#include "inferior.h"
+#include "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);
+ }
+}
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 81c4f0b4e37..2a37bd8861a 100644
--- a/gdb/mi/mi-cmds.h
+++ b/gdb/mi/mi-cmds.h
@@ -100,6 +100,7 @@ extern mi_cmd_argv_ftype mi_cmd_stack_list_frames;
extern mi_cmd_argv_ftype mi_cmd_stack_list_locals;
extern mi_cmd_argv_ftype mi_cmd_stack_list_variables;
extern mi_cmd_argv_ftype mi_cmd_stack_select_frame;
+extern mi_cmd_argv_ftype mi_cmd_shadow_stack_list_frames;
extern mi_cmd_argv_ftype mi_cmd_symbol_list_lines;
extern mi_cmd_argv_ftype mi_cmd_symbol_info_functions;
extern mi_cmd_argv_ftype mi_cmd_symbol_info_module_functions;
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 6515d4b182d..12d44a497a6 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,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. */
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index d4b0d53acf3..4eb8c53f995 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -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 */
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..cf302ae1a45
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp
@@ -0,0 +1,69 @@
+# Copyright 2024-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test the mi command -shadow-stack-list-frames for signal handling on linux.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+require allow_ssp_tests {istarget "*-*-linux*"}
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+ append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+ set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack-signal.c"
+ set testfile mi-shadow-stack
+
+ # Test shadow-stack-list-frames for shadow stack element which is no
+ # return address.
+ if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+ {debug additional_flags="-fcf-protection=return"}] } {
+ return
+ }
+
+ if { [mi_clean_restart $testfile] } {
+ return
+ }
+
+ mi_runto_main
+ mi_send_resuming_command "exec-continue" "continue till signal"
+
+ set r_signal "reason=\"signal-received\",signal-name=\"SIGUSR1\",signal-meaning=\"User defined signal 1\""
+ gdb_expect {
+ -re ".*stopped,${r_signal}.*$mi_gdb_prompt" {
+ pass "Wait for user interrupt"
+ }
+ timeout {
+ fail "Wait for user interrupt (timeout)"
+ return
+ }
+ }
+
+ mi_gdb_test "break handler" \
+ {(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done}
+
+ mi_execute_to "exec-continue" "breakpoint-hit" "handler" ".*" ".*" ".*" \
+ {"" "disp=\"keep\""} "continue to handler"
+
+ # We only test the frame belonging to the shadow stack element which
+ # is not a return address. This frame is 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.34.1
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
prev parent reply other threads:[~2026-07-08 14:39 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-08 14:36 [PATCH v4 00/13] Add new command to print the shadow stack backtrace Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 01/13] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 02/13] aarch64: Implement gdbarch function top_addr_empty_shadow_stack Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 03/13] gdb: Add get_main_func_start_pc to refactor frame.c:inside_main_func Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 04/13] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 05/13] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 06/13] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 07/13] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 08/13] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 09/13] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 10/13] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 11/13] gdb: Enable inferior calls in the shadow stack backtrace Christina Schimpe
2026-07-08 14:36 ` [PATCH v4 12/13] gdb: Enable signal trampolines " Christina Schimpe
2026-07-08 14:36 ` Christina Schimpe [this message]
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=20260708143639.2214689-14-christina.schimpe@intel.com \
--to=christina.schimpe@intel.com \
--cc=gdb-patches@sourceware.org \
--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