[PATCH v2 7/9] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements.

Christina Schimpe
2026-01-23T08:09:07Z

[-- Attachment #1: Type: text/plain, Size: 12524 bytes --]

On x86 with CET or on ARM with GCS, there can be elements on the shadow
stack which are not return addresses. In this case, we don't want to print
the shadow stack element, but a string instead which describes the frame
similar to the normal backtrace command for dummy frames or signals.

Provide a gdbarch hook to distinguish between return and non-return
addresses and to configure a string which is printed instead of the
shadow stack element.
---
gdb/doc/gdb.texinfo | 20 +++++++++++++
gdb/gdbarch-gen.c | 32 ++++++++++++++++++++
gdb/gdbarch-gen.h | 17 +++++++++++
gdb/gdbarch.h | 1 +
gdb/gdbarch_components.py | 21 +++++++++++++
gdb/shadow-stack.c | 63 ++++++++++++++-------------------------
gdb/shadow-stack.h | 40 +++++++++++++++++++++++++
7 files changed, 154 insertions(+), 40 deletions(-)

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index f50d96a09f3..f7dc7c4397d 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8772,6 +8772,26 @@ This is how a shadow stack backtrace looks like on amd64:
#4 0x0000555555555065 in _start
@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.
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index be6570ad97d..76e841698af 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -263,6 +263,7 @@ struct gdbarch
gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr;
int shadow_stack_element_size_aligned = 8;
gdbarch_get_shadow_stack_size_ftype *get_shadow_stack_size = nullptr;
+ 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
@@ -537,6 +538,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
/* Skip verify of top_addr_empty_shadow_stack, has predicate. */
/* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0. */
/* Skip verify of get_shadow_stack_size, has predicate. */
+ /* 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 ());
@@ -1420,6 +1422,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
gdb_printf (file,
"gdbarch_dump: get_shadow_stack_size = <%s>\n",
host_address_to_string (gdbarch->get_shadow_stack_size));
+ 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 != NULL)
gdbarch->dump_tdep (gdbarch, file);
}
@@ -5603,3 +5611,27 @@ set_gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch,
{
gdbarch->get_shadow_stack_size = get_shadow_stack_size;
}
+
+bool
+gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch)
+{
+ gdb_assert (gdbarch != NULL);
+ return gdbarch->is_no_return_shadow_stack_address != NULL;
+}
+
+bool
+gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const shadow_stack_frame_info &frame, std::string &frame_type)
+{
+ gdb_assert (gdbarch != NULL);
+ gdb_assert (gdbarch->is_no_return_shadow_stack_address != NULL);
+ 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, frame, frame_type);
+}
+
+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 155d60b1b8b..e24a5415ce0 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1843,3 +1843,20 @@ extern bool gdbarch_get_shadow_stack_size_p (struct gdbarch *gdbarch);
typedef long (gdbarch_get_shadow_stack_size_ftype) (struct gdbarch *gdbarch, const std::optional<CORE_ADDR> ssp, const std::pair<CORE_ADDR, CORE_ADDR> range);
extern long gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch, const std::optional<CORE_ADDR> ssp, const std::pair<CORE_ADDR, CORE_ADDR> range);
extern void set_gdbarch_get_shadow_stack_size (struct gdbarch *gdbarch, gdbarch_get_shadow_stack_size_ftype *get_shadow_stack_size);
+
+/* 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 options 'backtrace -shadow' and
+ the shadow stack can contain elements which are not return addresses, this
+ function has to be provided.
+
+ Return true, if FRAME does not contain a return address in FRAME->VALUE
+ but another valid value for the architecture's shadow stack. In this case,
+ also the string frame_type has to be configured to display the type in the
+ shadow stack backtrace. */
+
+extern bool gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch);
+
+typedef bool (gdbarch_is_no_return_shadow_stack_address_ftype) (struct gdbarch *gdbarch, const shadow_stack_frame_info &frame, std::string &frame_type);
+extern bool gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const shadow_stack_frame_info &frame, std::string &frame_type);
+extern 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 fe59846b916..6e966e12f21 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -31,6 +31,7 @@
#include "gdbsupport/gdb-checked-static-cast.h"
#include "registry.h"
#include "solib.h"
+#include "shadow-stack.h"

struct floatformat;
struct ui_file;
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index 1f24a3a6d72..d39cba6e016 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2911,3 +2911,24 @@ In case shadow stack is not enabled for the current thread, return -1.
],
predicate=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 options 'backtrace -shadow' and
+the shadow stack can contain elements which are not return addresses, this
+function has to be provided.
+
+Return true, if FRAME does not contain a return address in FRAME->VALUE
+but another valid value for the architecture's shadow stack. In this case,
+also the string frame_type has to be configured to display the type in the
+shadow stack backtrace.
+""",
+ type="bool",
+ name="is_no_return_shadow_stack_address",
+ params=[
+ ("const shadow_stack_frame_info &", "frame"),
+ ("std::string &", "frame_type")],
+ predicate=True,
+)
diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c
index 82d98c7336c..e930187604e 100644
--- a/gdb/shadow-stack.c
+++ b/gdb/shadow-stack.c
@@ -203,46 +203,6 @@ pc_in_middle_of_statement (CORE_ADDR pc, symtab_and_line sal)
return pc > sal.pc && pc <= sal.end;
}

-enum class ssp_unwind_stop_reason
-{
- /* No particular reason; either we haven't tried unwinding yet, or we
- didn't fail. */
- no_error = 0,
-
- /* We could not read the memory of the shadow stack element. */
- memory_read_error
-};
-
-/* Information of a shadow stack frame belonging to a shadow stack element
- at shadow stack pointer SSP. */
-
-class shadow_stack_frame_info
-{
-public:
- /* If possible, unwind the previous shadow stack frame info. RANGE is
- the shadow stack memory range [start_address, end_address) belonging
- to this frame's shadow stack pointer. If we cannot unwind the
- previous frame info, set the unwind_stop_reason attribute. If we
- reached the bottom of the shadow stack just don't return a value. */
- std::optional<shadow_stack_frame_info> unwind_prev_shadow_stack_frame_info
- (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range);
-
- /* The shadow stack pointer. */
- CORE_ADDR ssp;
-
- /* The value of the shadow stack at SSP. */
- CORE_ADDR value;
-
- /* The level of the element on the shadow stack. */
- unsigned long level;
-
- /* If unwinding of the previous frame info fails assign this value to a
- matching condition ssp_unwind_stop_reason
- > ssp_unwind_stop_reason::no_error. */
- ssp_unwind_stop_reason unwind_stop_reason
- = ssp_unwind_stop_reason::no_error;
-};
-
/* Attempt to obtain the function name based on the symbol of PC. */

static gdb::unique_xmalloc_ptr<char>
@@ -271,6 +231,29 @@ do_print_shadow_stack_frame_info
const frame_print_options &fp_opts,
const shadow_stack_frame_info &frame, print_what print_what)
{
+ std::string frame_type;
+ if (gdbarch_is_no_return_shadow_stack_address_p (gdbarch)
+ && gdbarch_is_no_return_shadow_stack_address (gdbarch,
+ frame,
+ frame_type))
+ {
+ gdb_assert (!frame_type.empty ());
+
+ /* It is possible, for the x86 architecture for instance, that an
+ element on the shadow stack is not a return address. We still
+ want to print the address in that case but no further
+ information. */
+ 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", frame_type, 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. */
diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h
index 5370becfc9a..24a6d48efa0 100644
--- a/gdb/shadow-stack.h
+++ b/gdb/shadow-stack.h
@@ -41,4 +41,44 @@ 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
+};
+
+/* Information of a shadow stack frame belonging to a shadow stack element
+ at shadow stack pointer SSP. */
+
+class shadow_stack_frame_info
+{
+public:
+ /* If possible, unwind the previous shadow stack frame info. RANGE is
+ the shadow stack memory range [start_address, end_address) belonging
+ to this frame's shadow stack pointer. If we cannot unwind the
+ previous frame info, set the unwind_stop_reason attribute. If we
+ reached the bottom of the shadow stack just don't return a value. */
+ std::optional<shadow_stack_frame_info> unwind_prev_shadow_stack_frame_info
+ (gdbarch *gdbarch, std::pair<CORE_ADDR, CORE_ADDR> range);
+
+ /* The shadow stack pointer. */
+ CORE_ADDR ssp;
+
+ /* The value of the shadow stack at SSP. */
+ CORE_ADDR value;
+
+ /* The level of the element on the shadow stack. */
+ unsigned long level;
+
+ /* If unwinding of the previous frame info fails assign this value to a
+ matching condition ssp_unwind_stop_reason
+ > ssp_unwind_stop_reason::no_error. */
+ ssp_unwind_stop_reason unwind_stop_reason
+ = ssp_unwind_stop_reason::no_error;
+};
+
#endif /* GDB_SHADOW_STACK_H */
--
2.34.1

[-- Attachment #2.1: Type: text/plain, Size: 329 bytes --]

Intel Deutschland GmbH
Registered Address: Dornacher Straße 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht München HRB 186928

[-- Attachment #2.2: Type: text/html, Size: 357 bytes --]