Re: [PATCH v2 6/9] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace.

Thiago Jung Bauermann
2026-04-10T06:21:50Z

Hello Christina,

"Schimpe, Christina" <christina.schimpe@intel.com> writes:

> Thanks a lot for your feedback.

You're welcome! Thank you for moving this forward. Sorry for the big
delays on my side. More things going on at once than I'd like.

> For the hook gdbarch_get_shadow_stack_size I still need your GCS implementation. 😊
> I suggest a separate patch for this with you as the only author and I'll include this in my v3
> then, too. Does that make sense?

I think you also need aarch64_linux_is_no_return_shadow_stack_address,
right? I'm including both in a a patch at the end of this email.

There are FIXMEs in them to remind myself to do something better than
assert if ssp is an invalid address. Perhaps throw an error as you
mention below.

Also, feel free to not provide the aarch64 versions of the hooks in this
patch and the following one. I was planning to send them after your
series goes in.

The reason I wanted an aarch64 hook in the first patch was so that
existing GCS functionality doesn't regress, but the hooks in the other
patches are for new functionality so it's fine to have only the amd64
implementation.

>> > + const unsigned long shadow_stack_bytes = range.second - *ssp;
>> > +
>> > + gdb_assert ((shadow_stack_bytes % 8) == 0);
>>
>> I don't think this should be an assert. If it fails, it triggers an internal error in
>> GDB. In this case it could indeed mean an internal error (GDB somehow got
>> the SSP or range wrong), but it could also be (and probably more likely) an
>> inconsistent state of the inferior. This can happen in a program being debugged
>> so GDB should be able to handle it gracefully, and if possible provide useful
>> information to the user.
>
> I agree. This is rather something that is outside GDB's control.
>
> From the documentation for internal errors:
> "Internal errors indicate programming errors such as assertion failures, as opposed to
> more general errors beyond the application's control. "
>
> So based on that I rather would choose a normal error, not an internal error.
> What do you think ?

The effect of the error being thrown would be just that the "bt -shadow"
command is interrupted, right? If so, I think it's a good idea.

>> > + return shadow_stack_bytes / 8;
>> > +}

--
Thiago

From 89f07938071f78c479ac045296afd562ab9ef93a Mon Sep 17 00:00:00 2001
From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
Date: Tue, 10 Mar 2026 21:44:35 -0300
Subject: [PATCH] WIP GDB: aarch64-linux: Add gdbarch hooks for printing shadow
stack

Enables bt -shadow.

There are still some FIXMEs to address.
---
gdb/aarch64-linux-tdep.c | 38 ++++++++++++++++++++++++++++++++++++++
gdb/aarch64-tdep.c | 20 ++++++++++++++++++++
gdb/arch/aarch64.h | 3 +++
3 files changed, 61 insertions(+)

diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index f37b28067b8a..e9c9b8480aac 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -2615,6 +2615,42 @@ aarch64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
return gcspr;
}

+/* Return true, if FRAME is a valid shadow stack frame while FRAME.VALUE
+ does not refer to a return address. This can happen, for instance, in
+ case of signals: a signal handling specific GCS cap token will be
+ written to the GCS. In case this is true, configure the string which
+ describes the frame and is displayed instead of the address in the
+ shadow stack backtrace. */
+
+static bool
+aarch64_linux_is_no_return_shadow_stack_address
+ (gdbarch *gdbarch,
+ const shadow_stack_frame_info &frame,
+ std::string &frame_type)
+{
+ /* FRAME must be a valid shadow stack frame. */
+ bool valid_addr
+ = gdbarch_address_in_shadow_stack_memory_range (gdbarch, frame.ssp,
+ nullptr);
+ /* FIXME: Shouldn't be an assert. */
+ gdb_assert (valid_addr == true);
+
+ /* If the GCS entry isn't a cap token, then it should be a return
+ address. */
+ if ((frame.value & AARCH64_GCS_CAP_ADDR_MASK) != frame.ssp)
+ return false;
+
+ /* When delivering a signal, the Linux kernel writes a cap token with the
+ token type (bits 0..11) all clear. */
+ if ((frame.value & AARCH64_GCS_CAP_TOKEN_MASK) == 0)
+ {
+ frame_type = _("<sigframe token>");
+ return true;
+ }
+
+ return false;
+}
+
/* AArch64 Linux implementation of the report_signal_info gdbarch
hook. Displays information about possible memory tag violations. */

@@ -3193,6 +3229,8 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
set_gdbarch_get_shadow_stack_pointer (gdbarch,
aarch64_linux_get_shadow_stack_pointer);
+ set_gdbarch_is_no_return_shadow_stack_address (gdbarch,
+ aarch64_linux_is_no_return_shadow_stack_address);
tdep->fn_prev_gcspr = dwarf2_prev_ssp;
}
}
diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 112d42c6a1ac..6bf63bcc1f97 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -1926,6 +1926,24 @@ aarch64_top_addr_empty_shadow_stack (gdbarch *gdbarch, const CORE_ADDR addr,
return addr >= range.second - 8;
}

+/* Return the number of elements which are currently on the shadow stack
+ based on the shadow stack memory RANGE [start_address, end_address)
+ of the current thread. In case shadow stack is not enabled for the
+ current thread, return -1. */
+
+static long
+aarch64_get_shadow_stack_size (gdbarch *gdbarch, const CORE_ADDR ssp,
+ const std::pair<CORE_ADDR, CORE_ADDR> range)
+{
+ const unsigned long shadow_stack_bytes = range.second - ssp;
+
+ /* FIXME: Shouldn't be an assert. */
+ gdb_assert ((shadow_stack_bytes % 8) == 0);
+
+ /* The oldest entry in the GCS isn't an address, just the value '0'. */
+ return shadow_stack_bytes / 8 - 1;
+}
+
/* Implement the "push_dummy_call" gdbarch method. */

static CORE_ADDR
@@ -4812,6 +4830,8 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base);
set_gdbarch_top_addr_empty_shadow_stack
(gdbarch, aarch64_top_addr_empty_shadow_stack);
+ set_gdbarch_get_shadow_stack_size (gdbarch,
+ aarch64_get_shadow_stack_size);
}

/* ABI */
diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h
index 0e9715a4268a..7c69bfd01feb 100644
--- a/gdb/arch/aarch64.h
+++ b/gdb/arch/aarch64.h
@@ -246,6 +246,9 @@ enum aarch64_regnum
/* Size of the SME2 ZT0 register in bytes. */
#define AARCH64_SME2_ZT0_SIZE 64

+#define AARCH64_GCS_CAP_TOKEN_MASK ((uint64_t) 0xFFF)
+#define AARCH64_GCS_CAP_ADDR_MASK ~AARCH64_GCS_CAP_TOKEN_MASK
+
/* Feature check for Floating Point Mode Register. */
#ifndef HWCAP2_FPMR
#define HWCAP2_FPMR (1ULL << 48)