[v4,10/13] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux.
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-arm |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
success
|
Test passed
|
Commit Message
There can be elements on the shadow stack which are not return addresses.
This can happen, for instance, in case of signals on amd64 linux.
The old shadow stack pointer is pushed in a special format with bit 63 set.
|1...old SSP| - Pointer to old pre-signal ssp in sigframe token format
(bit 63 set to 1)
Linux kernel documentation: https://docs.kernel.org/arch/x86/shstk.html
Implement the gdbarch hook is_no_return_shadow_stack_address to detect
this scenario to print the shadow stack backtrace correctly.
---
gdb/amd64-linux-tdep.c | 59 +++++++++++++++++++
.../amd64-shadow-stack-backtrace-signal.exp | 49 +++++++++++++++
.../gdb.arch/amd64-shadow-stack-signal.c | 31 ++++++++++
3 files changed, 139 insertions(+)
create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp
create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c
Comments
Christina Schimpe <christina.schimpe@intel.com> writes:
> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> index 42a62eaa975..d98415b8989 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -1960,6 +1960,62 @@ amd64_linux_top_addr_empty_shadow_stack
> return addr == range.second;
> }
>
> +/* Return a shadow stack frame info, 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. This can happen, for
> + instance, in case of signals. The old shadow stack pointer is pushed
> + in a special format with bit 63 set. In case this is true, a valid
> + shadow stack frame info is returned with its attributes frame_type and
> + non_return_description configured to ssp_frame_type::non_return_frame
> + and "<sigframe token>", respectively. */
> +
> +static std::optional<shadow_stack_frame_info>
> +amd64_linux_is_no_return_shadow_stack_address
> + (gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value,
> + const unsigned long level)
> +{
> + /* SSP must belong to the shadow stack memory range. */
> + std::pair<CORE_ADDR, CORE_ADDR> range;
> + gdb_assert (gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> + ssp,
> + &range));
I made this comment at another assert in a previous version of this
series, but it's valid here too:
Won't this make GDB crash with a misbehaving inferior? 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.
Maybe turn this into an error ()?
> +
> + /* In case bit 63 is not configured, the address on the shadow stack
> + should be a return address. */
> + constexpr CORE_ADDR mask = (CORE_ADDR) 1 << 63;
> + if ((value & mask) == 0)
> + return {};
> +
> + /* To compare the shadow stack pointer of the previous frame with the
> + value of FRAME, we must clear bit 63. */
> + CORE_ADDR shadow_stack_val_cleared = (value & (~mask));
> +
> + /* Compute the previous/old SSP. The shadow stack grows downwards. To
> + compute the previous shadow stack pointer, we need to increment
> + SSP. */
> + CORE_ADDR prev_ssp
> + = ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch);
> +
> + /* We incremented SSP by one element to compute PREV_SSP before. In
> + case SSP points to the first element of the shadow stack, PREV_SSP
> + must point to the bottom of the shadow stack (RANGE.SECOND), but not
> + beyond that address. */
> + gdb_assert (prev_ssp > range.first && prev_ssp <= range.second);
> +
> + if (shadow_stack_val_cleared == prev_ssp)
> + {
> + /* Assign the current gdbarch to the new shadow stack frame. Since
> + the token is no PC value, do not assign a SAL object. */
> + return std::optional<shadow_stack_frame_info>
> + ({ssp, value, level, gdbarch, {},
> + ssp_frame_type::non_return_frame,
> + {"<sigframe token>"},
> + ssp_unwind_stop_reason::no_error});
> + }
> +
> + return {};
> +}
> +
Hi Thiago,
Thank you for the review.
I have some questions to your feedback, please see below.
> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Donnerstag, 3. September 2026 09:50
> To: Joos, Christina <christina.joos@intel.com>
> Cc: gdb-patches@sourceware.org; tom@tromey.com
> Subject: Re: [PATCH v4 10/13] gdb: Implement the hook
> 'is_no_return_shadow_stack_address' for amd64 linux.
>
> Christina Schimpe <christina.schimpe@intel.com> writes:
>
> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> > 42a62eaa975..d98415b8989 100644
> > --- a/gdb/amd64-linux-tdep.c
> > +++ b/gdb/amd64-linux-tdep.c
> > @@ -1960,6 +1960,62 @@ amd64_linux_top_addr_empty_shadow_stack
> > return addr == range.second;
> > }
> >
> > +/* Return a shadow stack frame info, 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. This can happen, for
> > + instance, in case of signals. The old shadow stack pointer is pushed
> > + in a special format with bit 63 set. In case this is true, a valid
> > + shadow stack frame info is returned with its attributes frame_type and
> > + non_return_description configured to ssp_frame_type::non_return_frame
> > + and "<sigframe token>", respectively. */
> > +
> > +static std::optional<shadow_stack_frame_info>
> > +amd64_linux_is_no_return_shadow_stack_address
> > + (gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value,
> > + const unsigned long level)
> > +{
> > + /* SSP must belong to the shadow stack memory range. */
> > + std::pair<CORE_ADDR, CORE_ADDR> range;
> > + gdb_assert (gdbarch_address_in_shadow_stack_memory_range (gdbarch,
> > + ssp,
> > + &range));
>
> I made this comment at another assert in a previous version of this series, but
> it's valid here too:
Hm, I don't remember it... :/ Did I fix it already ? Or can you point me to your comment?
> Won't this make GDB crash with a misbehaving inferior? 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.
>
> Maybe turn this into an error ()?
Do you have an example program/debug session for this?
Then it would be easier for me to write a meaningful error message.
Thanks,
Christina
________________________________________
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 (89) 99143-0
www.intel.de
Managing Directors: Candice Moore, Jeffrey Schneiderman, Ramachandran Sitaraman
Chairperson of the Supervisory Board: Sonja Pierer
Registered Seat: Munich Commercial Register B: Amtsgericht Munich HRB 186928
This e-mail and any attachments may contain confidential material for
the sole use of the intended recipient(s). Any review or distribution
by others is strictly prohibited. If you are not the intended
recipient, please contact the sender and delete all copies.
Hello Christina,
"Joos, Christina" <christina.joos@intel.com> writes:
>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Donnerstag, 3. September 2026 09:50
>> To: Joos, Christina <christina.joos@intel.com>
>> Cc: gdb-patches@sourceware.org; tom@tromey.com
>> Subject: Re: [PATCH v4 10/13] gdb: Implement the hook
>> 'is_no_return_shadow_stack_address' for amd64 linux.
>>
>> Christina Schimpe <christina.schimpe@intel.com> writes:
>>
>> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
>> > 42a62eaa975..d98415b8989 100644
>> > --- a/gdb/amd64-linux-tdep.c
>> > +++ b/gdb/amd64-linux-tdep.c
>> > @@ -1960,6 +1960,62 @@ amd64_linux_top_addr_empty_shadow_stack
>> > return addr == range.second;
>> > }
>> >
>> > +/* Return a shadow stack frame info, 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. This can happen, for
>> > + instance, in case of signals. The old shadow stack pointer is pushed
>> > + in a special format with bit 63 set. In case this is true, a valid
>> > + shadow stack frame info is returned with its attributes frame_type and
>> > + non_return_description configured to ssp_frame_type::non_return_frame
>> > + and "<sigframe token>", respectively. */
>> > +
>> > +static std::optional<shadow_stack_frame_info>
>> > +amd64_linux_is_no_return_shadow_stack_address
>> > + (gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value,
>> > + const unsigned long level)
>> > +{
>> > + /* SSP must belong to the shadow stack memory range. */
>> > + std::pair<CORE_ADDR, CORE_ADDR> range;
>> > + gdb_assert (gdbarch_address_in_shadow_stack_memory_range (gdbarch,
>> > + ssp,
>> > + &range));
>>
>> I made this comment at another assert in a previous version of this series, but
>> it's valid here too:
>
> Hm, I don't remember it... :/ Did I fix it already ? Or can you point me to your comment?
It was during review of v2:
https://inbox.sourceware.org/gdb-patches/87bjh1y3xk.fsf@linaro.org/
The assert was in amd64_linux_get_shadow_stack_size, but that gdbarch
hook doesn't exist anymore.
Also, during review of a different patch series, Andrew Burgess said¹:
> We shouldn't assert on data from an outside source. This should be
> either an error, or a warning if GDB is able to handle this and push
> on.
Which I think is a similar concern. Though in that case the outside
source was a /proc file, not inferior state.
>> Won't this make GDB crash with a misbehaving inferior? 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.
>>
>> Maybe turn this into an error ()?
>
> Do you have an example program/debug session for this?
> Then it would be easier for me to write a meaningful error message.
I don't. While I read the patch I just wondered whether this assert
could trigger in a situation where the inferior is in a crazy or
corrupted state. <hand-wave>Maybe it tried to do some stack-switching
trick and got it wrong?</hand-wave>
An error message could be direct, maybe something like "SSP doesn't
point to a shadow stack memory region"...
> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Dienstag, 22. September 2026 05:46
> To: Joos, Christina <christina.joos@intel.com>
> Cc: gdb-patches@sourceware.org; tom@tromey.com
> Subject: Re: [PATCH v4 10/13] gdb: Implement the hook
> 'is_no_return_shadow_stack_address' for amd64 linux.
>
> Hello Christina,
>
> "Joos, Christina" <christina.joos@intel.com> writes:
>
> >> -----Original Message-----
> >> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> Sent: Donnerstag, 3. September 2026 09:50
> >> To: Joos, Christina <christina.joos@intel.com>
> >> Cc: gdb-patches@sourceware.org; tom@tromey.com
> >> Subject: Re: [PATCH v4 10/13] gdb: Implement the hook
> >> 'is_no_return_shadow_stack_address' for amd64 linux.
> >>
> >> Christina Schimpe <christina.schimpe@intel.com> writes:
> >>
> >> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> >> > 42a62eaa975..d98415b8989 100644
> >> > --- a/gdb/amd64-linux-tdep.c
> >> > +++ b/gdb/amd64-linux-tdep.c
> >> > @@ -1960,6 +1960,62 @@ amd64_linux_top_addr_empty_shadow_stack
> >> > return addr == range.second;
> >> > }
> >> >
> >> > +/* Return a shadow stack frame info, 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. This can happen, for
> >> > + instance, in case of signals. The old shadow stack pointer is pushed
> >> > + in a special format with bit 63 set. In case this is true, a valid
> >> > + shadow stack frame info is returned with its attributes frame_type and
> >> > + non_return_description configured to
> ssp_frame_type::non_return_frame
> >> > + and "<sigframe token>", respectively. */
> >> > +
> >> > +static std::optional<shadow_stack_frame_info>
> >> > +amd64_linux_is_no_return_shadow_stack_address
> >> > + (gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value,
> >> > + const unsigned long level)
> >> > +{
> >> > + /* SSP must belong to the shadow stack memory range. */
> >> > + std::pair<CORE_ADDR, CORE_ADDR> range;
> >> > + gdb_assert (gdbarch_address_in_shadow_stack_memory_range
> (gdbarch,
> >> > + ssp,
> >> > + &range));
> >>
> >> I made this comment at another assert in a previous version of this
> >> series, but it's valid here too:
> >
> > Hm, I don't remember it... :/ Did I fix it already ? Or can you point me to your
> comment?
>
> It was during review of v2:
>
> https://inbox.sourceware.org/gdb-patches/87bjh1y3xk.fsf@linaro.org/
>
> The assert was in amd64_linux_get_shadow_stack_size, but that gdbarch hook
> doesn't exist anymore.
>
> Also, during review of a different patch series, Andrew Burgess said¹:
>
> > We shouldn't assert on data from an outside source. This should be
> > either an error, or a warning if GDB is able to handle this and push
> > on.
>
> Which I think is a similar concern. Though in that case the outside source was a
> /proc file, not inferior state.
Ah, this makes a lot of sense to me. Thanks a lot for sharing that. The shadow stack
pointer is read using a ptrace call, so it is originally based on an outside source. It
just may have been updated in between. The range is based on /proc maps interface.
> >> Won't this make GDB crash with a misbehaving inferior? 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.
> >>
> >> Maybe turn this into an error ()?
> >
> > Do you have an example program/debug session for this?
> > Then it would be easier for me to write a meaningful error message.
>
> I don't. While I read the patch I just wondered whether this assert could trigger
> in a situation where the inferior is in a crazy or corrupted state. <hand-
> wave>Maybe it tried to do some stack-switching trick and got it
> wrong?</hand-wave>
>
> An error message could be direct, maybe something like "SSP doesn't point to
> a shadow stack memory region"...
Yes, sounds good. I will use that wording, but write "The shadow stack pointer" instead of "SSP":
error (_("The shadow stack pointer does not point to a shadow stack "
"memory region"));
> --
> Thiago
> (he/him)
>
> ¹ https://inbox.sourceware.org/gdb-patches/87a4ppdq5a.fsf@redhat.com/
Regards,
Christina
________________________________________
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 (89) 99143-0
www.intel.de
Managing Directors: Candice Moore, Jeffrey Schneiderman, Ramachandran Sitaraman
Chairperson of the Supervisory Board: Sonja Pierer
Registered Seat: Munich Commercial Register B: Amtsgericht Munich HRB 186928
This e-mail and any attachments may contain confidential material for
the sole use of the intended recipient(s). Any review or distribution
by others is strictly prohibited. If you are not the intended
recipient, please contact the sender and delete all copies.
@@ -1960,6 +1960,62 @@ amd64_linux_top_addr_empty_shadow_stack
return addr == range.second;
}
+/* Return a shadow stack frame info, 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. This can happen, for
+ instance, in case of signals. The old shadow stack pointer is pushed
+ in a special format with bit 63 set. In case this is true, a valid
+ shadow stack frame info is returned with its attributes frame_type and
+ non_return_description configured to ssp_frame_type::non_return_frame
+ and "<sigframe token>", respectively. */
+
+static std::optional<shadow_stack_frame_info>
+amd64_linux_is_no_return_shadow_stack_address
+ (gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value,
+ const unsigned long level)
+{
+ /* SSP must belong to the shadow stack memory range. */
+ std::pair<CORE_ADDR, CORE_ADDR> range;
+ gdb_assert (gdbarch_address_in_shadow_stack_memory_range (gdbarch,
+ ssp,
+ &range));
+
+ /* In case bit 63 is not configured, the address on the shadow stack
+ should be a return address. */
+ constexpr CORE_ADDR mask = (CORE_ADDR) 1 << 63;
+ if ((value & mask) == 0)
+ return {};
+
+ /* To compare the shadow stack pointer of the previous frame with the
+ value of FRAME, we must clear bit 63. */
+ CORE_ADDR shadow_stack_val_cleared = (value & (~mask));
+
+ /* Compute the previous/old SSP. The shadow stack grows downwards. To
+ compute the previous shadow stack pointer, we need to increment
+ SSP. */
+ CORE_ADDR prev_ssp
+ = ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch);
+
+ /* We incremented SSP by one element to compute PREV_SSP before. In
+ case SSP points to the first element of the shadow stack, PREV_SSP
+ must point to the bottom of the shadow stack (RANGE.SECOND), but not
+ beyond that address. */
+ gdb_assert (prev_ssp > range.first && prev_ssp <= range.second);
+
+ if (shadow_stack_val_cleared == prev_ssp)
+ {
+ /* Assign the current gdbarch to the new shadow stack frame. Since
+ the token is no PC value, do not assign a SAL object. */
+ return std::optional<shadow_stack_frame_info>
+ ({ssp, value, level, gdbarch, {},
+ ssp_frame_type::non_return_frame,
+ {"<sigframe token>"},
+ ssp_unwind_stop_reason::no_error});
+ }
+
+ return {};
+}
+
static void
amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
int num_disp_step_buffers)
@@ -2023,6 +2079,9 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
set_gdbarch_top_addr_empty_shadow_stack
(gdbarch, amd64_linux_top_addr_empty_shadow_stack);
+
+ set_gdbarch_is_no_return_shadow_stack_address
+ (gdbarch, amd64_linux_is_no_return_shadow_stack_address);
}
static void
new file mode 100644
@@ -0,0 +1,49 @@
+# 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 shadow stack backtrace for signal handling on linux.
+
+require allow_ssp_tests {istarget "*-*-linux*"}
+
+standard_testfile amd64-shadow-stack-signal.c
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+
+ append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+ if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+ {debug additional_flags="-fcf-protection=return"}] } {
+ return
+ }
+
+ if { ![runto_main] } {
+ return
+ }
+
+ gdb_breakpoint "handler"
+ gdb_test "continue" \
+ ".*Program received signal SIGUSR1, User defined signal 1.*" \
+ "continue until signal"
+ gdb_continue_to_breakpoint "continue to breakpoint in handler"
+
+ # Test shadow stack backtrace including <sigframe token>.
+ gdb_test "bt -shadow" \
+ [multi_line \
+ "#0\[ \t\]*$hex in \[^\r\n\]+" \
+ "#1\[ \t\]*<sigframe token>" \
+ "#2\[ \t\]*$hex in \[^\r\n\]+" \
+ ".*" ] \
+ "test shadow stack backtrace for signal handling."
+}
new file mode 100644
@@ -0,0 +1,31 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ 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/>. */
+
+#include <signal.h>
+
+void
+handler (int signo)
+{
+}
+
+int
+main (void)
+{
+ signal (SIGUSR1, handler);
+ raise (SIGUSR1);
+ return 0;
+}