From: Christina Schimpe <christina.schimpe@intel.com>
To: gdb-patches@sourceware.org
Cc: tom@tromey.com, thiago.bauermann@linaro.org
Subject: [PATCH v3 08/12] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.
Date: Mon, 6 Jul 2026 12:31:29 +0000 [thread overview]
Message-ID: <20260706123133.1990441-9-christina.schimpe@intel.com> (raw)
In-Reply-To: <20260706123133.1990441-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-entry} or
@samp{-hide} are not supported.
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
~~
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
/#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
~~
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/doc/gdb.texinfo | 43 ++
gdb/gdbarch-gen.c | 32 ++
gdb/gdbarch-gen.h | 21 +
gdb/gdbarch.h | 1 +
gdb/gdbarch_components.py | 27 +
gdb/shadow-stack.c | 509 ++++++++++++++++++
gdb/shadow-stack.h | 91 ++++
gdb/stack.c | 49 +-
gdb/stack.h | 43 ++
.../gdb.arch/amd64-shadow-stack-cmds.exp | 47 ++
gdb/testsuite/gdb.base/options.exp | 2 +-
12 files changed, 843 insertions(+), 25 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 343828a85dd..2605a5ab78a 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 the Common Trace Format (CTF) has been removed. GDB now
saves trace information exclusively in its own "tfile" format.
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index a698b2b8451..24b11d2657c 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8788,6 +8788,47 @@ 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 (@pxref{CET}) on x86 and the Guarded Control Stack
+feature (@pxref{GCS}) on AArch64.
+
+This option may be combined with @code{-frame-info} and implies
+@code{-no-filters} and @code{-frame-arguments none}. Also the options
+@samp{-past-entry} or @samp{-hide} are not supported.
+
+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
+@end group
+@end smallexample
+
+There can be elements on the shadow stack which are not return addresses,
+for example on x86 with the Intel Control-Flow Enforcement Technology
+(@xref{CET}). In case of signals, the old shadow stack pointer is pushed
+in a special format with bit 63 set. See @url{https://docs.kernel.org/arch/x86/shstk.html}
+for more details. For such shadow stack elements, the shadow stack
+frame just contains the level and a string describing the shadow stack
+element:
+
+@smallexample
+@group
+(gdb) bt -shadow 4
+#0 0x00007ffff7c45330 in __restore_rt from /lib/x86_64-linux-gnu/libc.so.6
+#1 <sigframe token>
+#2 0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:26
+#3 0x000055555555519d in main at tmp/amd64-shadow-stack-signal.c:29
+(More shadow stack frames follow...)
+@end group
+@end smallexample
+
@end table
The optional @var{qualifier} is maintained for backward compatibility.
@@ -27219,6 +27260,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}.
+@anchor{GCS}
@subsubsection AArch64 Guarded Control Stack
@cindex Guarded Control Stack, AArch64
@cindex GCS, AArch64
@@ -27307,6 +27349,7 @@ registers
@end itemize
+@anchor{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 022ad496026..e93afff34b1 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -256,6 +256,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_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address = nullptr;
};
/* Create a new ``struct gdbarch'' based on information provided by
@@ -519,6 +520,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 is_no_return_shadow_stack_address, has predicate. */
if (!log.empty ())
internal_error (_("verify_gdbarch: the following are invalid ...%s"),
log.c_str ());
@@ -1357,6 +1359,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_is_no_return_shadow_stack_address_p() = %d\n",
+ gdbarch_is_no_return_shadow_stack_address_p (gdbarch));
+ gdb_printf (file,
+ "gdbarch_dump: is_no_return_shadow_stack_address = <%s>\n",
+ host_address_to_string (gdbarch->is_no_return_shadow_stack_address));
if (gdbarch->dump_tdep != nullptr)
gdbarch->dump_tdep (gdbarch, file);
}
@@ -5362,3 +5370,27 @@ set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch,
{
gdbarch->shadow_stack_element_size_aligned = shadow_stack_element_size_aligned;
}
+
+bool
+gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch)
+{
+ gdb_assert (gdbarch != nullptr);
+ return gdbarch->is_no_return_shadow_stack_address != nullptr;
+}
+
+std::optional<shadow_stack_frame_info>
+gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level)
+{
+ gdb_assert (gdbarch != nullptr);
+ gdb_assert (gdbarch->is_no_return_shadow_stack_address != nullptr);
+ if (gdbarch_debug >= 2)
+ gdb_printf (gdb_stdlog, "gdbarch_is_no_return_shadow_stack_address called\n");
+ return gdbarch->is_no_return_shadow_stack_address (gdbarch, ssp, value, level);
+}
+
+void
+set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch,
+ gdbarch_is_no_return_shadow_stack_address_ftype is_no_return_shadow_stack_address)
+{
+ gdbarch->is_no_return_shadow_stack_address = is_no_return_shadow_stack_address;
+}
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index c05564fe05f..791324777dd 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1745,6 +1745,8 @@ void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarch_core_
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' each value and hook
+ listed above 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
@@ -1787,3 +1789,22 @@ void set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, gdbarch_t
int gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch);
void set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch, int shadow_stack_element_size_aligned);
+
+/* There can be elements on the shadow stack which are not return addresses.
+ This happens for example on x86 with CET in case of signals.
+ If an architecture implements the command option 'backtrace -shadow' and
+ the shadow stack can contain elements which are not return addresses, this
+ function has to be provided.
+ Return a shadow stack frame info with frame type
+ ssp_frame_type::non_return_frame, if the shadow stack pointer SSP belongs
+ to a valid shadow stack frame while the element on the shadow stack
+ VALUE does not refer to a return address. In that case, also the frame's
+ attribute non_return_description has to be set to a string which is
+ displayed instead of the element on the shadow stack in the shadow stack
+ backtrace. Otherwise, return an empty optional. */
+
+bool gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch);
+
+using gdbarch_is_no_return_shadow_stack_address_ftype = std::optional<shadow_stack_frame_info> (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level);
+std::optional<shadow_stack_frame_info> gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level);
+void set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address);
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 7077f9262e9..b587a862e90 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -32,6 +32,7 @@
#include "registry.h"
#include "solib.h"
#include "find-memory-region.h"
+#include "shadow-stack.h"
struct floatformat;
struct ui_file;
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index 531d7e212d8..0435b1b5eca 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2774,6 +2774,8 @@ provided:
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' each value and hook
+listed above 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
@@ -2837,3 +2839,28 @@ this value.
# linux starting with Linux kernel v6.6.
unused=True,
)
+
+Method(
+ comment="""
+There can be elements on the shadow stack which are not return addresses.
+This happens for example on x86 with CET in case of signals.
+If an architecture implements the command option 'backtrace -shadow' and
+the shadow stack can contain elements which are not return addresses, this
+function has to be provided.
+Return a shadow stack frame info with frame type
+ssp_frame_type::non_return_frame, if the shadow stack pointer SSP belongs
+to a valid shadow stack frame while the element on the shadow stack
+VALUE does not refer to a return address. In that case, also the frame's
+attribute non_return_description has to be set to a string which is
+displayed instead of the element on the shadow stack in the shadow stack
+backtrace. Otherwise, return an empty optional.
+""",
+ type="std::optional<shadow_stack_frame_info>",
+ name="is_no_return_shadow_stack_address",
+ params=[
+ ("const CORE_ADDR", "ssp"),
+ ("const CORE_ADDR", "value"),
+ ("const unsigned long", "level"),
+ ],
+ predicate=True,
+)
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 160d30e0bfb..dbd8959753d 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -22,6 +22,12 @@
#include "frame.h"
#include "frame-unwind.h"
#include "shadow-stack.h"
+#include "stack.h"
+#include "solib.h"
+#include "event-top.h"
+#include "cli/cli-style.h"
+#include "buffered-streams.h"
+#include "inferior.h"
enum class ssp_update_direction
{
@@ -162,3 +168,506 @@ 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;
+}
+
+/* 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, const frame_print_options &fp_opts,
+ const shadow_stack_frame_info &frame, print_what print_what)
+{
+ /* 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 (frame.arch);
+
+ if (frame.type != ssp_frame_type::normal_frame)
+ {
+ std::string str;
+ if (frame.type == ssp_frame_type::non_return_frame)
+ {
+ /* For non-return frames, the string must have a value. */
+ gdb_assert (frame.non_return_description.has_value ());
+ str = frame.non_return_description.value ();
+ }
+ else
+ gdb_assert_not_reached ("Invalid shadow stack frame type.");
+
+ ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame");
+ uiout->text ("#");
+ uiout->field_fmt_signed (2, ui_left, "level", frame.level);
+
+ uiout->field_string ("func", str, metadata_style.style ());
+
+ uiout->text ("\n");
+ gdb_flush (gdb_stdout);
+ return;
+ }
+
+ if (fp_opts.print_frame_info != print_frame_info_auto)
+ {
+ /* Use the specific frame information desired by the user. */
+ print_what
+ = *print_frame_info_to_print_what (fp_opts.print_frame_info);
+ }
+
+ /* At this point, since FRAME is a shadow stack frame which belongs to a
+ return address, we should always have a valid symtab_and_line object. */
+ gdb_assert (frame.sal.has_value ());
+
+ const symtab_and_line &sal = frame.sal.value ();
+ if (should_print_location (print_what) || sal.symtab == nullptr)
+ {
+ gdb::unique_xmalloc_ptr<char> funname = find_pc_funname (frame.value);
+
+ ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame");
+ uiout->text ("#");
+ uiout->field_fmt_signed (2, ui_left, "level", frame.level);
+
+ uiout->field_string
+ ("addr", hex_string_custom (frame.value, element_size * 2),
+ address_style.style ());
+
+ uiout->text (" in ");
+ print_funname (uiout, funname);
+
+ if (print_what != SHORT_LOCATION && sal.symtab != nullptr)
+ print_filename (uiout, sal);
+
+ 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);
+ }
+
+ 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
+ print configuration, for shadow stack backtrace we always print
+ the pc/address on the shadow stack. */
+ bool print_address = true;
+ print_source (uiout, frame.arch, frame.value, sal, print_address,
+ mid_statement, "");
+ }
+
+ 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
+ (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,
+ 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 of success, return true. Otherwise,
+ return false. */
+
+static bool
+read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp,
+ CORE_ADDR &return_value)
+{
+ /* 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);
+ return safe_read_memory_unsigned_integer (ssp,
+ element_size,
+ byte_order,
+ &return_value);
+}
+
+/* If possible, return the starting shadow stack frame info needed to handle
+ COUNT outermost frames. This is the equivalent function to
+ stack.c:trailing_outermost_frame which is used to print the normal backtrace.
+ 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
+ (const std::pair<CORE_ADDR, CORE_ADDR> range,
+ LONGEST count, shadow_stack_frame_info &frame)
+{
+ gdb_assert (count > 0);
+
+ std::optional<shadow_stack_frame_info> trailing
+ = std::optional<shadow_stack_frame_info> (frame);
+
+ std::optional<shadow_stack_frame_info> current = trailing;
+ while (current.has_value () && count--)
+ {
+ QUIT;
+ current = current->unwind_prev_shadow_stack_frame_info (range);
+ }
+
+ /* Will stop when CURRENT reaches the outermost (oldest) element of the
+ shadow stack. TRAILING will be COUNT below it. */
+ while (current.has_value ())
+ {
+ QUIT;
+ trailing = trailing->unwind_prev_shadow_stack_frame_info (range);
+ /* Since current has a value until this point we can assume that
+ trailing also has a value. */
+ gdb_assert (trailing.has_value ());
+ current = current->unwind_prev_shadow_stack_frame_info (range);
+ }
+
+ 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. */
+
+static std::optional<shadow_stack_frame_info>
+get_shadow_stack_frame_info
+ (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level)
+{
+ CORE_ADDR value;
+ if (!read_shadow_stack_memory (fallback_arch, ssp, value))
+ return {};
+
+ if (gdbarch_is_no_return_shadow_stack_address_p (fallback_arch))
+ {
+ std::optional<shadow_stack_frame_info> no_return_frame
+ = gdbarch_is_no_return_shadow_stack_address (fallback_arch, ssp,
+ value, level);
+ if (no_return_frame.has_value ())
+ return *no_return_frame;
+ }
+
+ /* At this point, we know that SSP points to VALUE which is a return
+ address. In contrast to find_frame_sal which is used for the normal
+ backtrace command, VALUE 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 (value, 1);
+ struct gdbarch *sal_arch = get_sal_arch (sal);
+
+ /* Fall back to the previous gdbarch in case we cannot extract it from
+ SAL. */
+ if (sal_arch == nullptr)
+ sal_arch = fallback_arch;
+
+ return std::optional<shadow_stack_frame_info>
+ ({ssp, value, level, sal_arch, sal,
+ ssp_frame_type::normal_frame, {},
+ ssp_unwind_stop_reason::no_error});
+}
+
+std::optional<shadow_stack_frame_info>
+shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info
+ (std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+ if (this->type == ssp_frame_type::normal_frame
+ && !user_set_backtrace_options.backtrace_past_main
+ && this->inside_main_func ())
+ {
+ /* Don't unwind past main(). */
+ return {};
+ }
+
+ /* 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 (this->arch, this->ssp,
+ ssp_update_direction::outer);
+
+ if (gdbarch_top_addr_empty_shadow_stack_p (this->arch)
+ && gdbarch_top_addr_empty_shadow_stack (this->arch, new_ssp, range))
+ return {};
+ else if (gdbarch_stack_grows_down (this->arch))
+ {
+ /* 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);
+ }
+
+ /* Extract the previous shadow stack frame info (level + 1). */
+ std::optional<shadow_stack_frame_info> new_frame
+ = get_shadow_stack_frame_info (this->arch, new_ssp, this->level + 1);
+
+ if (!new_frame.has_value ())
+ this->unwind_stop_reason = ssp_unwind_stop_reason::memory_read_error;
+
+ return new_frame;
+}
+
+bool
+shadow_stack_frame_info::inside_main_func () const
+{
+ language lang;
+ symbol *sym = find_symbol_for_pc (this->value);
+ if (sym != nullptr)
+ lang = sym->language ();
+ else
+ {
+ bound_minimal_symbol msymbol
+ = lookup_minimal_symbol_by_pc (this->value);
+
+ if (msymbol.minsym != nullptr)
+ lang = msymbol.minsym->language ();
+ else
+ {
+ /* If we cannot extract the symbol, we cannot use the address to
+ find out if the function is inside main and we better print
+ this frame, too. */
+ return false;
+ }
+ }
+
+ std::optional<CORE_ADDR> sym_addr
+ = get_main_func_start_pc (this->arch, lang);
+ if (!sym_addr.has_value () || *sym_addr == 0)
+ return false;
+
+ CORE_ADDR pc_func_start = get_pc_function_start (this->value);
+ if (*sym_addr == pc_func_start)
+ return true;
+
+ return false;
+}
+
+/* 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_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
+ = get_shadow_stack_frame_info (gdbarch, *start_ssp, 0);
+
+ if (!current.has_value ())
+ reason = ssp_unwind_stop_reason::memory_read_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
+ (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));
+ }
+
+ for (current = trailing; current.has_value () && count != 0; count--)
+ {
+ QUIT;
+
+ print_shadow_stack_frame_info (fp_opts, *current, LOCATION);
+
+ trailing = current;
+ current = current->unwind_prev_shadow_stack_frame_info (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 (trailing->arch, 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 5f540ff7d62..4278620c70c 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -21,9 +21,17 @@
#ifndef GDB_SHADOW_STACK_H
#define GDB_SHADOW_STACK_H
+#include <optional>
+#include <utility>
+#include <string>
+
+#include "symtab.h"
+
class regcache;
class frame_info_ptr;
struct value;
+struct frame_print_options;
+struct gdbarch;
/* If shadow stack is enabled, push the address NEW_ADDR on the shadow
stack and update the shadow stack pointer accordingly. */
@@ -39,4 +47,87 @@ 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" command line option. */
+
+void backtrace_shadow_command
+ (const frame_print_options &fp_opts,
+ const char *count_exp, int from_tty);
+
+enum class ssp_unwind_stop_reason
+{
+ /* No particular reason; either we haven't tried unwinding yet, or we
+ didn't fail. */
+ no_error = 0,
+
+ /* We could not read the memory of the shadow stack element. */
+ memory_read_error,
+};
+
+enum class ssp_frame_type
+{
+ /* A normal shadow stack frame which belongs to a return address of the
+ program execution flow. */
+ normal_frame,
+
+ /* A shadow stack frame which does not belong to a return address.
+ It is possible, on x86 for instance, that an element on the shadow
+ stack is not a return address. We don't want to print the address
+ in that case but only a string describing that specific frame type.
+ This frame type is configured in the target specific implementation
+ of is_no_return_shadow_stack_address. */
+ non_return_frame,
+};
+
+/* 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
+ (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;
+
+ /* Architecture of the current shadow stack frame. */
+ struct gdbarch *arch;
+
+ /* Optional SAL object of the current shadow stack frame. */
+ std::optional<symtab_and_line> sal;
+
+ /* The shadow stack frame type. */
+ ssp_frame_type type;
+
+ /* If this is not a normal shadow stack frame belonging to a return
+ address of the program execution flow then the frame will have the
+ type ssp_frame_type::non_return_frame. In this case, this string
+ is configured to describe this specific frame type and it will
+ be printed in the shadow stack backtrace instead of the element on
+ the shadow stack. */
+ std::optional<std::string> non_return_description;
+
+ /* 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;
+
+ /* Return true if this frame is inside the "main"() function. If the frame
+ is outside the "main"() function, does not have a SAL object or if the
+ symbol information cannot be extracted, return false. */
+ bool inside_main_func () const;
+};
+
#endif /* GDB_SHADOW_STACK_H */
diff --git a/gdb/stack.c b/gdb/stack.c
index 7df1d6e33da..e96fbe73947 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -51,6 +51,7 @@
#include "cli/cli-utils.h"
#include "objfiles.h"
#include "buffered-streams.h"
+#include "shadow-stack.h"
#include "symfile.h"
#include "extension.h"
@@ -191,6 +192,7 @@ struct backtrace_cmd_options
bool full = false;
bool no_filters = false;
bool hide = false;
+ bool shadow = false;
};
using bt_flag_option_def
@@ -214,6 +216,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. */
@@ -906,11 +916,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++)
@@ -960,7 +968,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
@@ -969,14 +977,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)
@@ -1242,7 +1245,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;
@@ -1308,10 +1311,9 @@ 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
+void
print_lib (ui_out *uiout, const char *lib)
{
annotate_frame_where ();
@@ -1320,10 +1322,7 @@ print_lib (ui_out *uiout, const char *lib)
uiout->field_string ("from", lib, file_name_style.style ());
}
-/* Print the filenname of SAL to UIOUT for the printing of frame
- information. */
-
-static void
+void
print_filename (ui_out *uiout, symtab_and_line sal)
{
annotate_frame_source_begin ();
@@ -1348,10 +1347,9 @@ print_filename (ui_out *uiout, symtab_and_line sal)
annotate_frame_source_end ();
}
-/* 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)
{
@@ -2162,7 +2160,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..3f4cf348841 100644
--- a/gdb/stack.h
+++ b/gdb/stack.h
@@ -84,4 +84,47 @@ void frame_apply_all_cmd_completer (struct cmd_list_element *ignore,
completion_tracker &tracker,
const char *text, const char */*word*/);
+/* Print the filename of SAL to UIOUT for the printing of frame
+ information. */
+
+void print_filename (ui_out *uiout, symtab_and_line sal);
+
+/* Print the library LIB to UIOUT for the printing of frame
+ information. */
+
+void print_lib (ui_out *uiout, const char *lib);
+
+/* If available, print FUNNAME to UIOUT for the printing of frame
+ information. */
+
+void print_funname (ui_out *uiout,
+ gdb::unique_xmalloc_ptr<char> const &funname);
+
+/* 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);
+
#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 e67a0c2e4a1..0347313b647 100644
--- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
@@ -109,6 +109,53 @@ 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"
+
+ # Test bt -shadow, which prints until the main function.
+ gdb_test "bt -shadow" \
+ [multi_line \
+ "$frame1" \
+ "$frame2" ] \
+ "test shadow stack backtrace until main."
+
+ # With -past-main set, we can still 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 -past-main" \
+ [multi_line \
+ "$frame1" \
+ "$frame2" \
+ "$frame3" \
+ ".*" ] \
+ "test shadow stack backtrace until the bottom of the stack."
+
+ gdb_test "bt -shadow 1" \
+ [multi_line \
+ "$frame1" \
+ "\\(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"
+
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 35487ead6a7..8ca6e677aba 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 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
next prev parent reply other threads:[~2026-07-06 12:35 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 12:31 [PATCH v3 00/12] Add new command " Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 01/12] gdb: Generalize handling of the shadow stack pointer Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 02/12] aarch64: Implement gdbarch function top_addr_empty_shadow_stack Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 03/12] gdb: Add get_main_func_start_pc to refactor frame.c:inside_main_func Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 04/12] gdb: Refactor 'stack.c:print_frame' Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 05/12] gdb: Introduce 'stack.c:print_pc' function without frame argument Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 06/12] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 07/12] gdb: Refactor 'stack.c:print_frame_info' Christina Schimpe
2026-07-06 12:31 ` Christina Schimpe [this message]
2026-07-08 9:13 ` [PATCH v3 08/12] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace Schimpe, Christina
2026-07-06 12:31 ` [PATCH v3 09/12] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 10/12] gdb: Enable inferior calls in the shadow stack backtrace Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 11/12] gdb: Enable signal trampolines " Christina Schimpe
2026-07-06 12:31 ` [PATCH v3 12/12] gdb, mi: Add -shadow-stack-list-frames command Christina Schimpe
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=20260706123133.1990441-9-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