[08/12] gdb: Handle shadow stack pointer register unwinding for amd64 linux.

Message ID 20241220200501.324191-9-christina.schimpe@intel.com
State New
Headers
Series Add CET shadow stack support |

Commit Message

Schimpe, Christina Dec. 20, 2024, 8:04 p.m. UTC
  Unwind the $pl3_ssp register.
We now have an updated value for the shadow stack pointer when
moving up or down the frame level.  Note that $pl3_ssp can
become unavailable when moving to a frame before the shadow
stack enablement.  In the example below, shadow stack is enabled
in the function 'call1'.  Thus, when moving to a frame level above
the function, $pl3_ssp will become unavaiable.
Following the restriction of the linux kernel, implement the unwinding
for amd64 linux only.

Before this patch:
~~~
Breakpoint 1, call2 (j=3) at sample.c:44
44	  return 42;
(gdb) p $pl3_ssp
$1 = (void *) 0x7ffff79ffff8
(gdb) up
55	  call2 (3);
(gdb) p $pl3_ssp
$2 = (void *) 0x7ffff79ffff8
(gdb) up
68	  call1 (43);
(gdb) p $pl3_ssp
$3 = (void *) 0x7ffff79ffff8
~~~

After this patch:
~~~
Breakpoint 1, call2 (j=3) at sample.c:44
44	  return 42;
(gdb) p $pl3_ssp
$1 = (void *) 0x7ffff79ffff8
(gdb) up
55	  call2 (3);
(gdb) p $pl3_ssp
$2 = (void *) 0x7ffff7a00000
(gdb) up
68	  call1 (43i);
(gdb) p $pl3_ssp
$3 = <unavailable>
~~~

As we now have an updated value for each selected frame, the
return command is now enabled for shadow stack enabled programs, too.

We therefore add a test for the return command and shadow stack support,
and for an updated shadow stack pointer after a frame level change.
---
 gdb/amd64-linux-tdep.c                        | 69 +++++++++++++++
 gdb/linux-tdep.c                              | 47 ++++++++++
 gdb/linux-tdep.h                              |  7 ++
 .../gdb.arch/amd64-shadow-stack-cmds.exp      | 88 +++++++++++++++++++
 gdb/testsuite/gdb.arch/amd64-shadow-stack.c   | 13 +++
 5 files changed, 224 insertions(+)
 create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
  

Comments

Guinevere Larsen Jan. 30, 2025, 2:29 p.m. UTC | #1
On 12/20/24 5:04 PM, Schimpe, Christina wrote:
> Unwind the $pl3_ssp register.
> We now have an updated value for the shadow stack pointer when
> moving up or down the frame level.  Note that $pl3_ssp can
> become unavailable when moving to a frame before the shadow
> stack enablement.  In the example below, shadow stack is enabled
> in the function 'call1'.  Thus, when moving to a frame level above
> the function, $pl3_ssp will become unavaiable.
> Following the restriction of the linux kernel, implement the unwinding
> for amd64 linux only.
>
> Before this patch:
> ~~~
> Breakpoint 1, call2 (j=3) at sample.c:44
> 44	  return 42;
> (gdb) p $pl3_ssp
> $1 = (void *) 0x7ffff79ffff8
> (gdb) up
> 55	  call2 (3);
> (gdb) p $pl3_ssp
> $2 = (void *) 0x7ffff79ffff8
> (gdb) up
> 68	  call1 (43);
> (gdb) p $pl3_ssp
> $3 = (void *) 0x7ffff79ffff8
> ~~~
>
> After this patch:
> ~~~
> Breakpoint 1, call2 (j=3) at sample.c:44
> 44	  return 42;
> (gdb) p $pl3_ssp
> $1 = (void *) 0x7ffff79ffff8
> (gdb) up
> 55	  call2 (3);
> (gdb) p $pl3_ssp
> $2 = (void *) 0x7ffff7a00000
> (gdb) up
> 68	  call1 (43i);
> (gdb) p $pl3_ssp
> $3 = <unavailable>
> ~~~
>
> As we now have an updated value for each selected frame, the
> return command is now enabled for shadow stack enabled programs, too.
>
> We therefore add a test for the return command and shadow stack support,
> and for an updated shadow stack pointer after a frame level change.
> ---
>   gdb/amd64-linux-tdep.c                        | 69 +++++++++++++++
>   gdb/linux-tdep.c                              | 47 ++++++++++
>   gdb/linux-tdep.h                              |  7 ++
>   .../gdb.arch/amd64-shadow-stack-cmds.exp      | 88 +++++++++++++++++++
>   gdb/testsuite/gdb.arch/amd64-shadow-stack.c   | 13 +++
>   5 files changed, 224 insertions(+)
>   create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
>
> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> index 95f643b1217..895feac85e8 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -45,6 +45,8 @@
>   #include "arch/amd64-linux-tdesc.h"
>   #include "inferior.h"
>   #include "x86-tdep.h"
> +#include "dwarf2/frame.h"
> +#include "frame-unwind.h"
>   
>   /* The syscall's XML filename for i386.  */
>   #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
> @@ -1873,6 +1875,72 @@ amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch,
>     return (addr & amd64_linux_lam_untag_mask ());
>   }
>   
> +static value *
> +amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame,
> +			     void **this_cache, int regnum)
> +{
> +  value *v = frame_unwind_got_register (this_frame, regnum, regnum);
> +  gdb_assert (v != nullptr);
> +
> +  gdbarch *gdbarch = get_frame_arch (this_frame);
> +
> +  if (v->entirely_available () && !v->optimized_out ())
> +    {
> +      int size = register_size (gdbarch, regnum);
> +      bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +      CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (),
> +						size, byte_order);
> +
> +      /* Starting with v6.6., the Linux kernel supports CET shadow stack.
> +	 Using /proc/PID/smaps we can only check if the current shadow
> +	 stack pointer SSP points to shadow stack memory.  Only if this is
> +	 the case a valid previous shadow stack pointer can be
> +	 calculated.  */
> +      std::pair<CORE_ADDR, CORE_ADDR> range;
> +      if (linux_address_in_shadow_stack_mem_range (ssp, &range))
> +	{
> +	  /* The shadow stack grows downwards.  To compute the previous
> +	     shadow stack pointer, we need to increment SSP.
> +	     For x32 the shadow stack elements are still 64-bit aligned.
> +	     Thus, we cannot use gdbarch_addr_bit to compute the new stack
> +	     pointer.  */
> +	  const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
> +	  const int bytes_per_word
> +	    = (binfo->bits_per_word / binfo->bits_per_byte);
In patch 10 of this series, you introduce 
amd64_linux_shadow_stack_element_size_aligned to simplify this 
calculation. is there any reason you didn't introduce it here?
  
Schimpe, Christina Jan. 30, 2025, 4:11 p.m. UTC | #2
> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Thursday, January 30, 2025 3:29 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Subject: Re: [PATCH 08/12] gdb: Handle shadow stack pointer register unwinding
> for amd64 linux.
> 
> On 12/20/24 5:04 PM, Schimpe, Christina wrote:
> > Unwind the $pl3_ssp register.
> > We now have an updated value for the shadow stack pointer when moving
> > up or down the frame level.  Note that $pl3_ssp can become unavailable
> > when moving to a frame before the shadow stack enablement.  In the
> > example below, shadow stack is enabled in the function 'call1'.  Thus,
> > when moving to a frame level above the function, $pl3_ssp will become
> > unavaiable.
> > Following the restriction of the linux kernel, implement the unwinding
> > for amd64 linux only.
> >
> > Before this patch:
> > ~~~
> > Breakpoint 1, call2 (j=3) at sample.c:44
> > 44	  return 42;
> > (gdb) p $pl3_ssp
> > $1 = (void *) 0x7ffff79ffff8
> > (gdb) up
> > 55	  call2 (3);
> > (gdb) p $pl3_ssp
> > $2 = (void *) 0x7ffff79ffff8
> > (gdb) up
> > 68	  call1 (43);
> > (gdb) p $pl3_ssp
> > $3 = (void *) 0x7ffff79ffff8
> > ~~~
> >
> > After this patch:
> > ~~~
> > Breakpoint 1, call2 (j=3) at sample.c:44
> > 44	  return 42;
> > (gdb) p $pl3_ssp
> > $1 = (void *) 0x7ffff79ffff8
> > (gdb) up
> > 55	  call2 (3);
> > (gdb) p $pl3_ssp
> > $2 = (void *) 0x7ffff7a00000
> > (gdb) up
> > 68	  call1 (43i);
> > (gdb) p $pl3_ssp
> > $3 = <unavailable>
> > ~~~
> >
> > As we now have an updated value for each selected frame, the return
> > command is now enabled for shadow stack enabled programs, too.
> >
> > We therefore add a test for the return command and shadow stack
> > support, and for an updated shadow stack pointer after a frame level change.
> > ---
> >   gdb/amd64-linux-tdep.c                        | 69 +++++++++++++++
> >   gdb/linux-tdep.c                              | 47 ++++++++++
> >   gdb/linux-tdep.h                              |  7 ++
> >   .../gdb.arch/amd64-shadow-stack-cmds.exp      | 88 +++++++++++++++++++
> >   gdb/testsuite/gdb.arch/amd64-shadow-stack.c   | 13 +++
> >   5 files changed, 224 insertions(+)
> >   create mode 100644
> > gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> >
> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> > 95f643b1217..895feac85e8 100644
> > --- a/gdb/amd64-linux-tdep.c
> > +++ b/gdb/amd64-linux-tdep.c
> > @@ -45,6 +45,8 @@
> >   #include "arch/amd64-linux-tdesc.h"
> >   #include "inferior.h"
> >   #include "x86-tdep.h"
> > +#include "dwarf2/frame.h"
> > +#include "frame-unwind.h"
> >
> >   /* The syscall's XML filename for i386.  */
> >   #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
> > @@ -1873,6 +1875,72 @@
> amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch,
> >     return (addr & amd64_linux_lam_untag_mask ());
> >   }
> >
> > +static value *
> > +amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame,
> > +			     void **this_cache, int regnum) {
> > +  value *v = frame_unwind_got_register (this_frame, regnum, regnum);
> > +  gdb_assert (v != nullptr);
> > +
> > +  gdbarch *gdbarch = get_frame_arch (this_frame);
> > +
> > +  if (v->entirely_available () && !v->optimized_out ())
> > +    {
> > +      int size = register_size (gdbarch, regnum);
> > +      bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> > +      CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (),
> > +						size, byte_order);
> > +
> > +      /* Starting with v6.6., the Linux kernel supports CET shadow stack.
> > +	 Using /proc/PID/smaps we can only check if the current shadow
> > +	 stack pointer SSP points to shadow stack memory.  Only if this is
> > +	 the case a valid previous shadow stack pointer can be
> > +	 calculated.  */
> > +      std::pair<CORE_ADDR, CORE_ADDR> range;
> > +      if (linux_address_in_shadow_stack_mem_range (ssp, &range))
> > +	{
> > +	  /* The shadow stack grows downwards.  To compute the previous
> > +	     shadow stack pointer, we need to increment SSP.
> > +	     For x32 the shadow stack elements are still 64-bit aligned.
> > +	     Thus, we cannot use gdbarch_addr_bit to compute the new stack
> > +	     pointer.  */
> > +	  const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
> > +	  const int bytes_per_word
> > +	    = (binfo->bits_per_word / binfo->bits_per_byte);
> In patch 10 of this series, you introduce
> amd64_linux_shadow_stack_element_size_aligned to simplify this calculation. is
> there any reason you didn't introduce it here?

Thanks a lot for looking at this.

The reason is that at this state of the series I only had one occurrence of this
particular code line and its comment. To avoid duplication I decided to make a 
small function for it in patch 10, but before it seemed to introduce more
overhead. Would that make sense to you? 

Christina
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  
Guinevere Larsen Jan. 30, 2025, 4:13 p.m. UTC | #3
On 1/30/25 1:11 PM, Schimpe, Christina wrote:
>> -----Original Message-----
>> From: Guinevere Larsen <guinevere@redhat.com>
>> Sent: Thursday, January 30, 2025 3:29 PM
>> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>> patches@sourceware.org
>> Subject: Re: [PATCH 08/12] gdb: Handle shadow stack pointer register unwinding
>> for amd64 linux.
>>
>> On 12/20/24 5:04 PM, Schimpe, Christina wrote:
>>> Unwind the $pl3_ssp register.
>>> We now have an updated value for the shadow stack pointer when moving
>>> up or down the frame level.  Note that $pl3_ssp can become unavailable
>>> when moving to a frame before the shadow stack enablement.  In the
>>> example below, shadow stack is enabled in the function 'call1'.  Thus,
>>> when moving to a frame level above the function, $pl3_ssp will become
>>> unavaiable.
>>> Following the restriction of the linux kernel, implement the unwinding
>>> for amd64 linux only.
>>>
>>> Before this patch:
>>> ~~~
>>> Breakpoint 1, call2 (j=3) at sample.c:44
>>> 44	  return 42;
>>> (gdb) p $pl3_ssp
>>> $1 = (void *) 0x7ffff79ffff8
>>> (gdb) up
>>> 55	  call2 (3);
>>> (gdb) p $pl3_ssp
>>> $2 = (void *) 0x7ffff79ffff8
>>> (gdb) up
>>> 68	  call1 (43);
>>> (gdb) p $pl3_ssp
>>> $3 = (void *) 0x7ffff79ffff8
>>> ~~~
>>>
>>> After this patch:
>>> ~~~
>>> Breakpoint 1, call2 (j=3) at sample.c:44
>>> 44	  return 42;
>>> (gdb) p $pl3_ssp
>>> $1 = (void *) 0x7ffff79ffff8
>>> (gdb) up
>>> 55	  call2 (3);
>>> (gdb) p $pl3_ssp
>>> $2 = (void *) 0x7ffff7a00000
>>> (gdb) up
>>> 68	  call1 (43i);
>>> (gdb) p $pl3_ssp
>>> $3 = <unavailable>
>>> ~~~
>>>
>>> As we now have an updated value for each selected frame, the return
>>> command is now enabled for shadow stack enabled programs, too.
>>>
>>> We therefore add a test for the return command and shadow stack
>>> support, and for an updated shadow stack pointer after a frame level change.
>>> ---
>>>    gdb/amd64-linux-tdep.c                        | 69 +++++++++++++++
>>>    gdb/linux-tdep.c                              | 47 ++++++++++
>>>    gdb/linux-tdep.h                              |  7 ++
>>>    .../gdb.arch/amd64-shadow-stack-cmds.exp      | 88 +++++++++++++++++++
>>>    gdb/testsuite/gdb.arch/amd64-shadow-stack.c   | 13 +++
>>>    5 files changed, 224 insertions(+)
>>>    create mode 100644
>>> gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
>>>
>>> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
>>> 95f643b1217..895feac85e8 100644
>>> --- a/gdb/amd64-linux-tdep.c
>>> +++ b/gdb/amd64-linux-tdep.c
>>> @@ -45,6 +45,8 @@
>>>    #include "arch/amd64-linux-tdesc.h"
>>>    #include "inferior.h"
>>>    #include "x86-tdep.h"
>>> +#include "dwarf2/frame.h"
>>> +#include "frame-unwind.h"
>>>
>>>    /* The syscall's XML filename for i386.  */
>>>    #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
>>> @@ -1873,6 +1875,72 @@
>> amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch,
>>>      return (addr & amd64_linux_lam_untag_mask ());
>>>    }
>>>
>>> +static value *
>>> +amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame,
>>> +			     void **this_cache, int regnum) {
>>> +  value *v = frame_unwind_got_register (this_frame, regnum, regnum);
>>> +  gdb_assert (v != nullptr);
>>> +
>>> +  gdbarch *gdbarch = get_frame_arch (this_frame);
>>> +
>>> +  if (v->entirely_available () && !v->optimized_out ())
>>> +    {
>>> +      int size = register_size (gdbarch, regnum);
>>> +      bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>>> +      CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (),
>>> +						size, byte_order);
>>> +
>>> +      /* Starting with v6.6., the Linux kernel supports CET shadow stack.
>>> +	 Using /proc/PID/smaps we can only check if the current shadow
>>> +	 stack pointer SSP points to shadow stack memory.  Only if this is
>>> +	 the case a valid previous shadow stack pointer can be
>>> +	 calculated.  */
>>> +      std::pair<CORE_ADDR, CORE_ADDR> range;
>>> +      if (linux_address_in_shadow_stack_mem_range (ssp, &range))
>>> +	{
>>> +	  /* The shadow stack grows downwards.  To compute the previous
>>> +	     shadow stack pointer, we need to increment SSP.
>>> +	     For x32 the shadow stack elements are still 64-bit aligned.
>>> +	     Thus, we cannot use gdbarch_addr_bit to compute the new stack
>>> +	     pointer.  */
>>> +	  const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
>>> +	  const int bytes_per_word
>>> +	    = (binfo->bits_per_word / binfo->bits_per_byte);
>> In patch 10 of this series, you introduce
>> amd64_linux_shadow_stack_element_size_aligned to simplify this calculation. is
>> there any reason you didn't introduce it here?
> Thanks a lot for looking at this.
>
> The reason is that at this state of the series I only had one occurrence of this
> particular code line and its comment. To avoid duplication I decided to make a
> small function for it in patch 10, but before it seemed to introduce more
> overhead. Would that make sense to you?
>
This makes sense, but in that case I think it's better to just create 
this function at this point, so that code doesn't get created and 
deleted unnecessarily.
  
Schimpe, Christina Jan. 30, 2025, 4:40 p.m. UTC | #4
> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Thursday, January 30, 2025 5:14 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Subject: Re: [PATCH 08/12] gdb: Handle shadow stack pointer register unwinding
> for amd64 linux.
> 
> On 1/30/25 1:11 PM, Schimpe, Christina wrote:
> >> -----Original Message-----
> >> From: Guinevere Larsen <guinevere@redhat.com>
> >> Sent: Thursday, January 30, 2025 3:29 PM
> >> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> >> patches@sourceware.org
> >> Subject: Re: [PATCH 08/12] gdb: Handle shadow stack pointer register
> >> unwinding for amd64 linux.
> >>
> >> On 12/20/24 5:04 PM, Schimpe, Christina wrote:
> >>> Unwind the $pl3_ssp register.
> >>> We now have an updated value for the shadow stack pointer when
> >>> moving up or down the frame level.  Note that $pl3_ssp can become
> >>> unavailable when moving to a frame before the shadow stack
> >>> enablement.  In the example below, shadow stack is enabled in the
> >>> function 'call1'.  Thus, when moving to a frame level above the
> >>> function, $pl3_ssp will become unavaiable.
> >>> Following the restriction of the linux kernel, implement the
> >>> unwinding for amd64 linux only.
> >>>
> >>> Before this patch:
> >>> ~~~
> >>> Breakpoint 1, call2 (j=3) at sample.c:44
> >>> 44	  return 42;
> >>> (gdb) p $pl3_ssp
> >>> $1 = (void *) 0x7ffff79ffff8
> >>> (gdb) up
> >>> 55	  call2 (3);
> >>> (gdb) p $pl3_ssp
> >>> $2 = (void *) 0x7ffff79ffff8
> >>> (gdb) up
> >>> 68	  call1 (43);
> >>> (gdb) p $pl3_ssp
> >>> $3 = (void *) 0x7ffff79ffff8
> >>> ~~~
> >>>
> >>> After this patch:
> >>> ~~~
> >>> Breakpoint 1, call2 (j=3) at sample.c:44
> >>> 44	  return 42;
> >>> (gdb) p $pl3_ssp
> >>> $1 = (void *) 0x7ffff79ffff8
> >>> (gdb) up
> >>> 55	  call2 (3);
> >>> (gdb) p $pl3_ssp
> >>> $2 = (void *) 0x7ffff7a00000
> >>> (gdb) up
> >>> 68	  call1 (43i);
> >>> (gdb) p $pl3_ssp
> >>> $3 = <unavailable>
> >>> ~~~
> >>>
> >>> As we now have an updated value for each selected frame, the return
> >>> command is now enabled for shadow stack enabled programs, too.
> >>>
> >>> We therefore add a test for the return command and shadow stack
> >>> support, and for an updated shadow stack pointer after a frame level change.
> >>> ---
> >>>    gdb/amd64-linux-tdep.c                        | 69 +++++++++++++++
> >>>    gdb/linux-tdep.c                              | 47 ++++++++++
> >>>    gdb/linux-tdep.h                              |  7 ++
> >>>    .../gdb.arch/amd64-shadow-stack-cmds.exp      | 88
> +++++++++++++++++++
> >>>    gdb/testsuite/gdb.arch/amd64-shadow-stack.c   | 13 +++
> >>>    5 files changed, 224 insertions(+)
> >>>    create mode 100644
> >>> gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> >>>
> >>> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> >>> 95f643b1217..895feac85e8 100644
> >>> --- a/gdb/amd64-linux-tdep.c
> >>> +++ b/gdb/amd64-linux-tdep.c
> >>> @@ -45,6 +45,8 @@
> >>>    #include "arch/amd64-linux-tdesc.h"
> >>>    #include "inferior.h"
> >>>    #include "x86-tdep.h"
> >>> +#include "dwarf2/frame.h"
> >>> +#include "frame-unwind.h"
> >>>
> >>>    /* The syscall's XML filename for i386.  */
> >>>    #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
> >>> @@ -1873,6 +1875,72 @@
> >> amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch,
> >>>      return (addr & amd64_linux_lam_untag_mask ());
> >>>    }
> >>>
> >>> +static value *
> >>> +amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame,
> >>> +			     void **this_cache, int regnum) {
> >>> +  value *v = frame_unwind_got_register (this_frame, regnum,
> >>> +regnum);
> >>> +  gdb_assert (v != nullptr);
> >>> +
> >>> +  gdbarch *gdbarch = get_frame_arch (this_frame);
> >>> +
> >>> +  if (v->entirely_available () && !v->optimized_out ())
> >>> +    {
> >>> +      int size = register_size (gdbarch, regnum);
> >>> +      bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> >>> +      CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (),
> >>> +						size, byte_order);
> >>> +
> >>> +      /* Starting with v6.6., the Linux kernel supports CET shadow stack.
> >>> +	 Using /proc/PID/smaps we can only check if the current shadow
> >>> +	 stack pointer SSP points to shadow stack memory.  Only if this is
> >>> +	 the case a valid previous shadow stack pointer can be
> >>> +	 calculated.  */
> >>> +      std::pair<CORE_ADDR, CORE_ADDR> range;
> >>> +      if (linux_address_in_shadow_stack_mem_range (ssp, &range))
> >>> +	{
> >>> +	  /* The shadow stack grows downwards.  To compute the previous
> >>> +	     shadow stack pointer, we need to increment SSP.
> >>> +	     For x32 the shadow stack elements are still 64-bit aligned.
> >>> +	     Thus, we cannot use gdbarch_addr_bit to compute the new stack
> >>> +	     pointer.  */
> >>> +	  const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
> >>> +	  const int bytes_per_word
> >>> +	    = (binfo->bits_per_word / binfo->bits_per_byte);
> >> In patch 10 of this series, you introduce
> >> amd64_linux_shadow_stack_element_size_aligned to simplify this
> >> calculation. is there any reason you didn't introduce it here?
> > Thanks a lot for looking at this.
> >
> > The reason is that at this state of the series I only had one
> > occurrence of this particular code line and its comment. To avoid
> > duplication I decided to make a small function for it in patch 10, but
> > before it seemed to introduce more overhead. Would that make sense to you?
> >
> This makes sense, but in that case I think it's better to just create this function at
> this point, so that code doesn't get created and deleted unnecessarily.

Mh right, that's also true. I will apply your feedback in the next version of this series.

Christina
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  
Thiago Jung Bauermann Feb. 6, 2025, 3:30 a.m. UTC | #5
"Schimpe, Christina" <christina.schimpe@intel.com> writes:

> Unwind the $pl3_ssp register.
> We now have an updated value for the shadow stack pointer when
> moving up or down the frame level.  Note that $pl3_ssp can
> become unavailable when moving to a frame before the shadow
> stack enablement.  In the example below, shadow stack is enabled
> in the function 'call1'.  Thus, when moving to a frame level above
> the function, $pl3_ssp will become unavaiable.
> Following the restriction of the linux kernel, implement the unwinding
> for amd64 linux only.
>
> Before this patch:
> ~~~
> Breakpoint 1, call2 (j=3) at sample.c:44
> 44	  return 42;
> (gdb) p $pl3_ssp
> $1 = (void *) 0x7ffff79ffff8
> (gdb) up
> 55	  call2 (3);
> (gdb) p $pl3_ssp
> $2 = (void *) 0x7ffff79ffff8
> (gdb) up
> 68	  call1 (43);
> (gdb) p $pl3_ssp
> $3 = (void *) 0x7ffff79ffff8
> ~~~
>
> After this patch:
> ~~~
> Breakpoint 1, call2 (j=3) at sample.c:44
> 44	  return 42;
> (gdb) p $pl3_ssp
> $1 = (void *) 0x7ffff79ffff8
> (gdb) up
> 55	  call2 (3);
> (gdb) p $pl3_ssp
> $2 = (void *) 0x7ffff7a00000
> (gdb) up
> 68	  call1 (43i);
> (gdb) p $pl3_ssp
> $3 = <unavailable>
> ~~~
>
> As we now have an updated value for each selected frame, the
> return command is now enabled for shadow stack enabled programs, too.
>
> We therefore add a test for the return command and shadow stack support,
> and for an updated shadow stack pointer after a frame level change.
> ---
>  gdb/amd64-linux-tdep.c                        | 69 +++++++++++++++
>  gdb/linux-tdep.c                              | 47 ++++++++++
>  gdb/linux-tdep.h                              |  7 ++
>  .../gdb.arch/amd64-shadow-stack-cmds.exp      | 88 +++++++++++++++++++
>  gdb/testsuite/gdb.arch/amd64-shadow-stack.c   | 13 +++
>  5 files changed, 224 insertions(+)
>  create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp

Just some minor comments.

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>

> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> index 95f643b1217..895feac85e8 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -45,6 +45,8 @@
>  #include "arch/amd64-linux-tdesc.h"
>  #include "inferior.h"
>  #include "x86-tdep.h"
> +#include "dwarf2/frame.h"
> +#include "frame-unwind.h"
>
>  /* The syscall's XML filename for i386.  */
>  #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
> @@ -1873,6 +1875,72 @@ amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch,
>    return (addr & amd64_linux_lam_untag_mask ());
>  }
>
> +static value *
> +amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame,
> +			     void **this_cache, int regnum)
> +{

Add documentation comment to this function.

> +  value *v = frame_unwind_got_register (this_frame, regnum, regnum);
> +  gdb_assert (v != nullptr);
> +
> +  gdbarch *gdbarch = get_frame_arch (this_frame);
> +
> +  if (v->entirely_available () && !v->optimized_out ())
> +    {
> +      int size = register_size (gdbarch, regnum);
> +      bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +      CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (),
> +						size, byte_order);
> +
> +      /* Starting with v6.6., the Linux kernel supports CET shadow stack.
> +	 Using /proc/PID/smaps we can only check if the current shadow
> +	 stack pointer SSP points to shadow stack memory.  Only if this is
> +	 the case a valid previous shadow stack pointer can be
> +	 calculated.  */
> +      std::pair<CORE_ADDR, CORE_ADDR> range;
> +      if (linux_address_in_shadow_stack_mem_range (ssp, &range))
> +	{
> +	  /* The shadow stack grows downwards.  To compute the previous
> +	     shadow stack pointer, we need to increment SSP.
> +	     For x32 the shadow stack elements are still 64-bit aligned.
> +	     Thus, we cannot use gdbarch_addr_bit to compute the new stack
> +	     pointer.  */
> +	  const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
> +	  const int bytes_per_word
> +	    = (binfo->bits_per_word / binfo->bits_per_byte);
> +	  CORE_ADDR new_ssp = ssp + bytes_per_word;

I agree with Guinevere's comment about introducing
amd64_linux_shadow_stack_element_size_aligned in this patch.

> +	  /* If NEW_SSP points to the end of or before (<=) the current
> +	     shadow stack memory range we consider NEW_SSP as valid (but
> +	     empty).  */
> +	  if (new_ssp <= range.second)
> +	    return frame_unwind_got_address (this_frame, regnum, new_ssp);
> +	}
> +    }
> +
> +  /* Return a value which is marked as unavailable in case we could not
> +     calculate a valid previous shadow stack pointer.  */
> +  value *retval
> +    = value::allocate_register (get_next_frame_sentinel_okay (this_frame),
> +				regnum, register_type (gdbarch, regnum));
> +  retval->mark_bytes_unavailable (0, retval->type ()->length ());
> +  return retval;
> +}
> +
> +static void
> +amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg *reg,
> +		const frame_info_ptr &this_frame)
> +{
> +  if (regnum == gdbarch_pc_regnum (gdbarch))
> +    reg->how = DWARF2_FRAME_REG_RA;
> +  else if (regnum == gdbarch_sp_regnum (gdbarch))
> +    reg->how = DWARF2_FRAME_REG_CFA;
> +  else if (regnum == AMD64_PL3_SSP_REGNUM)
> +    {
> +      reg->how = DWARF2_FRAME_REG_FN;
> +      reg->loc.fn = amd64_linux_dwarf2_prev_ssp;
> +    }
> +}

Add documentation comment to this function.

--
Thiago
  
Schimpe, Christina Feb. 6, 2025, 2:40 p.m. UTC | #6
> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Thursday, February 6, 2025 4:30 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH 08/12] gdb: Handle shadow stack pointer register unwinding
> for amd64 linux.
> 
> 
> "Schimpe, Christina" <christina.schimpe@intel.com> writes:
> 
> > Unwind the $pl3_ssp register.
> > We now have an updated value for the shadow stack pointer when moving
> > up or down the frame level.  Note that $pl3_ssp can become unavailable
> > when moving to a frame before the shadow stack enablement.  In the
> > example below, shadow stack is enabled in the function 'call1'.  Thus,
> > when moving to a frame level above the function, $pl3_ssp will become
> > unavaiable.
> > Following the restriction of the linux kernel, implement the unwinding
> > for amd64 linux only.
> >
> > Before this patch:
> > ~~~
> > Breakpoint 1, call2 (j=3) at sample.c:44
> > 44	  return 42;
> > (gdb) p $pl3_ssp
> > $1 = (void *) 0x7ffff79ffff8
> > (gdb) up
> > 55	  call2 (3);
> > (gdb) p $pl3_ssp
> > $2 = (void *) 0x7ffff79ffff8
> > (gdb) up
> > 68	  call1 (43);
> > (gdb) p $pl3_ssp
> > $3 = (void *) 0x7ffff79ffff8
> > ~~~
> >
> > After this patch:
> > ~~~
> > Breakpoint 1, call2 (j=3) at sample.c:44
> > 44	  return 42;
> > (gdb) p $pl3_ssp
> > $1 = (void *) 0x7ffff79ffff8
> > (gdb) up
> > 55	  call2 (3);
> > (gdb) p $pl3_ssp
> > $2 = (void *) 0x7ffff7a00000
> > (gdb) up
> > 68	  call1 (43i);
> > (gdb) p $pl3_ssp
> > $3 = <unavailable>
> > ~~~
> >
> > As we now have an updated value for each selected frame, the return
> > command is now enabled for shadow stack enabled programs, too.
> >
> > We therefore add a test for the return command and shadow stack
> > support, and for an updated shadow stack pointer after a frame level change.
> > ---
> >  gdb/amd64-linux-tdep.c                        | 69 +++++++++++++++
> >  gdb/linux-tdep.c                              | 47 ++++++++++
> >  gdb/linux-tdep.h                              |  7 ++
> >  .../gdb.arch/amd64-shadow-stack-cmds.exp      | 88 +++++++++++++++++++
> >  gdb/testsuite/gdb.arch/amd64-shadow-stack.c   | 13 +++
> >  5 files changed, 224 insertions(+)
> >  create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> 
> Just some minor comments.
> 
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> 
> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> > 95f643b1217..895feac85e8 100644
> > --- a/gdb/amd64-linux-tdep.c
> > +++ b/gdb/amd64-linux-tdep.c
> > @@ -45,6 +45,8 @@
> >  #include "arch/amd64-linux-tdesc.h"
> >  #include "inferior.h"
> >  #include "x86-tdep.h"
> > +#include "dwarf2/frame.h"
> > +#include "frame-unwind.h"
> >
> >  /* The syscall's XML filename for i386.  */  #define
> > XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
> > @@ -1873,6 +1875,72 @@
> amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch,
> >    return (addr & amd64_linux_lam_untag_mask ());  }
> >
> > +static value *
> > +amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame,
> > +			     void **this_cache, int regnum) {
> 
> Add documentation comment to this function.
> 
> > +  value *v = frame_unwind_got_register (this_frame, regnum, regnum);
> > + gdb_assert (v != nullptr);
> > +
> > +  gdbarch *gdbarch = get_frame_arch (this_frame);
> > +
> > +  if (v->entirely_available () && !v->optimized_out ())
> > +    {
> > +      int size = register_size (gdbarch, regnum);
> > +      bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> > +      CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (),
> > +						size, byte_order);
> > +
> > +      /* Starting with v6.6., the Linux kernel supports CET shadow stack.
> > +	 Using /proc/PID/smaps we can only check if the current shadow
> > +	 stack pointer SSP points to shadow stack memory.  Only if this is
> > +	 the case a valid previous shadow stack pointer can be
> > +	 calculated.  */
> > +      std::pair<CORE_ADDR, CORE_ADDR> range;
> > +      if (linux_address_in_shadow_stack_mem_range (ssp, &range))
> > +	{
> > +	  /* The shadow stack grows downwards.  To compute the previous
> > +	     shadow stack pointer, we need to increment SSP.
> > +	     For x32 the shadow stack elements are still 64-bit aligned.
> > +	     Thus, we cannot use gdbarch_addr_bit to compute the new stack
> > +	     pointer.  */
> > +	  const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
> > +	  const int bytes_per_word
> > +	    = (binfo->bits_per_word / binfo->bits_per_byte);
> > +	  CORE_ADDR new_ssp = ssp + bytes_per_word;
> 
> I agree with Guinevere's comment about introducing
> amd64_linux_shadow_stack_element_size_aligned in this patch.
> 
> > +	  /* If NEW_SSP points to the end of or before (<=) the current
> > +	     shadow stack memory range we consider NEW_SSP as valid (but
> > +	     empty).  */
> > +	  if (new_ssp <= range.second)
> > +	    return frame_unwind_got_address (this_frame, regnum, new_ssp);
> > +	}
> > +    }
> > +
> > +  /* Return a value which is marked as unavailable in case we could not
> > +     calculate a valid previous shadow stack pointer.  */
> > +  value *retval
> > +    = value::allocate_register (get_next_frame_sentinel_okay (this_frame),
> > +				regnum, register_type (gdbarch, regnum));
> > +  retval->mark_bytes_unavailable (0, retval->type ()->length ());
> > +  return retval;
> > +}
> > +
> > +static void
> > +amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg
> *reg,
> > +		const frame_info_ptr &this_frame)
> > +{
> > +  if (regnum == gdbarch_pc_regnum (gdbarch))
> > +    reg->how = DWARF2_FRAME_REG_RA;
> > +  else if (regnum == gdbarch_sp_regnum (gdbarch))
> > +    reg->how = DWARF2_FRAME_REG_CFA;
> > +  else if (regnum == AMD64_PL3_SSP_REGNUM)
> > +    {
> > +      reg->how = DWARF2_FRAME_REG_FN;
> > +      reg->loc.fn = amd64_linux_dwarf2_prev_ssp;
> > +    }
> > +}
> 
> Add documentation comment to this function.
> 
> --
> Thiago

Thank you for the review. I'll apply all your comments in the next version.

Christina
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  

Patch

diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index 95f643b1217..895feac85e8 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -45,6 +45,8 @@ 
 #include "arch/amd64-linux-tdesc.h"
 #include "inferior.h"
 #include "x86-tdep.h"
+#include "dwarf2/frame.h"
+#include "frame-unwind.h"
 
 /* The syscall's XML filename for i386.  */
 #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
@@ -1873,6 +1875,72 @@  amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch,
   return (addr & amd64_linux_lam_untag_mask ());
 }
 
+static value *
+amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame,
+			     void **this_cache, int regnum)
+{
+  value *v = frame_unwind_got_register (this_frame, regnum, regnum);
+  gdb_assert (v != nullptr);
+
+  gdbarch *gdbarch = get_frame_arch (this_frame);
+
+  if (v->entirely_available () && !v->optimized_out ())
+    {
+      int size = register_size (gdbarch, regnum);
+      bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+      CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (),
+						size, byte_order);
+
+      /* Starting with v6.6., the Linux kernel supports CET shadow stack.
+	 Using /proc/PID/smaps we can only check if the current shadow
+	 stack pointer SSP points to shadow stack memory.  Only if this is
+	 the case a valid previous shadow stack pointer can be
+	 calculated.  */
+      std::pair<CORE_ADDR, CORE_ADDR> range;
+      if (linux_address_in_shadow_stack_mem_range (ssp, &range))
+	{
+	  /* The shadow stack grows downwards.  To compute the previous
+	     shadow stack pointer, we need to increment SSP.
+	     For x32 the shadow stack elements are still 64-bit aligned.
+	     Thus, we cannot use gdbarch_addr_bit to compute the new stack
+	     pointer.  */
+	  const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
+	  const int bytes_per_word
+	    = (binfo->bits_per_word / binfo->bits_per_byte);
+	  CORE_ADDR new_ssp = ssp + bytes_per_word;
+
+	  /* If NEW_SSP points to the end of or before (<=) the current
+	     shadow stack memory range we consider NEW_SSP as valid (but
+	     empty).  */
+	  if (new_ssp <= range.second)
+	    return frame_unwind_got_address (this_frame, regnum, new_ssp);
+	}
+    }
+
+  /* Return a value which is marked as unavailable in case we could not
+     calculate a valid previous shadow stack pointer.  */
+  value *retval
+    = value::allocate_register (get_next_frame_sentinel_okay (this_frame),
+				regnum, register_type (gdbarch, regnum));
+  retval->mark_bytes_unavailable (0, retval->type ()->length ());
+  return retval;
+}
+
+static void
+amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg *reg,
+		const frame_info_ptr &this_frame)
+{
+  if (regnum == gdbarch_pc_regnum (gdbarch))
+    reg->how = DWARF2_FRAME_REG_RA;
+  else if (regnum == gdbarch_sp_regnum (gdbarch))
+    reg->how = DWARF2_FRAME_REG_CFA;
+  else if (regnum == AMD64_PL3_SSP_REGNUM)
+    {
+      reg->how = DWARF2_FRAME_REG_FN;
+      reg->loc.fn = amd64_linux_dwarf2_prev_ssp;
+    }
+}
+
 static void
 amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch,
 			    int num_disp_step_buffers)
@@ -1927,6 +1995,7 @@  amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch,
 
   set_gdbarch_remove_non_address_bits_watchpoint
     (gdbarch, amd64_linux_remove_non_address_bits_watchpoint);
+  dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
 }
 
 static void
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index d3452059ce2..3a20b12c3d7 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -47,6 +47,7 @@ 
 
 #include <ctype.h>
 #include <unordered_map>
+#include <algorithm>
 
 /* This enum represents the values that the user can choose when
    informing the Linux kernel about which memory mappings will be
@@ -96,6 +97,10 @@  struct smaps_vmflags
     /* Memory map has memory tagging enabled.  */
 
     unsigned int memory_tagging : 1;
+
+    /* Memory map used for shadow stack.  */
+
+    unsigned int shadow_stack_memory : 1;
   };
 
 /* Data structure that holds the information contained in the
@@ -537,6 +542,8 @@  decode_vmflags (char *p, struct smaps_vmflags *v)
 	v->shared_mapping = 1;
       else if (strcmp (s, "mt") == 0)
 	v->memory_tagging = 1;
+      else if (strcmp (s, "ss") == 0)
+	v->shadow_stack_memory = 1;
     }
 }
 
@@ -2744,6 +2751,46 @@  show_dump_excluded_mappings (struct ui_file *file, int from_tty,
 		      " flag is %s.\n"), value);
 }
 
+/* See linux-tdep.h.  */
+
+bool
+linux_address_in_shadow_stack_mem_range
+  (CORE_ADDR addr, std::pair<CORE_ADDR, CORE_ADDR> *range)
+{
+  if (!target_has_execution () || current_inferior ()->fake_pid_p)
+    return false;
+
+  const int pid = current_inferior ()->pid;
+
+  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+
+  gdb::unique_xmalloc_ptr<char> data
+    = target_fileio_read_stralloc (nullptr, smaps_file.c_str ());
+
+  if (data == nullptr)
+    return false;
+
+  const std::vector<smaps_data> smaps
+    = parse_smaps_data (data.get (), std::move (smaps_file));
+
+  auto find_addr_mem_range = [&addr] (const smaps_data &map)
+    {
+      bool addr_in_mem_range
+	= (addr >= map.start_address && addr < map.end_address);
+      return (addr_in_mem_range && map.vmflags.shadow_stack_memory);
+    };
+  auto it = std::find_if (smaps.begin (), smaps.end (), find_addr_mem_range);
+
+  if (it != smaps.end ())
+    {
+      range->first = it->start_address;
+      range->second = it->end_address;
+      return true;
+    }
+
+  return false;
+}
+
 /* To be called from the various GDB_OSABI_LINUX handlers for the
    various GNU/Linux architectures and machine types.
 
diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h
index bf4220bf75b..97dcc75a79c 100644
--- a/gdb/linux-tdep.h
+++ b/gdb/linux-tdep.h
@@ -117,4 +117,11 @@  extern CORE_ADDR linux_get_hwcap2 ();
 extern struct link_map_offsets *linux_ilp32_fetch_link_map_offsets ();
 extern struct link_map_offsets *linux_lp64_fetch_link_map_offsets ();
 
+/* Returns true if ADDR belongs to a shadow stack memory range.  If this
+   is the case, assign the shadow stack memory range to RANGE
+   [start_address, end_address).  */
+
+extern bool linux_address_in_shadow_stack_mem_range
+  (CORE_ADDR addr, std::pair<CORE_ADDR, CORE_ADDR> *range);
+
 #endif /* GDB_LINUX_TDEP_H */
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
new file mode 100644
index 00000000000..17f32ce3964
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
@@ -0,0 +1,88 @@ 
+# Copyright 2018-2024 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 enabling for frame level update and the return command.
+
+require allow_ssp_tests
+
+standard_testfile amd64-shadow-stack.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 -1
+    }
+
+    clean_restart ${binfile}
+    if { ![runto_main] } {
+	return -1
+    }
+
+    set call1_line [ gdb_get_line_number "break call1" ]
+    set call2_line [ gdb_get_line_number "break call2" ]
+
+    # Extract shadow stack pointer inside main, call1 and call2 function.
+    gdb_breakpoint $call1_line
+    gdb_breakpoint $call2_line
+    set ssp_main [get_valueof /x "\$pl3_ssp" 0 "get value of ssp in main"]
+    gdb_continue_to_breakpoint "break call1" ".*break call1.*"
+    set ssp_call1 [get_valueof /x "\$pl3_ssp" 0 "get value of ssp in call1"]
+    gdb_continue_to_breakpoint "break call2" ".*break call2.*"
+    set ssp_call2 [get_valueof /x "\$pl3_ssp" 0 "get value of ssp in call2"]
+
+    with_test_prefix "test frame level update" {
+	gdb_test "up" "call1.*" "move to frame 1"
+	gdb_test "print /x \$pl3_ssp" "= $ssp_call1" "check pl3_ssp of frame 1"
+	gdb_test "up" "main.*" "move to frame 2"
+	gdb_test "print /x \$pl3_ssp" "= $ssp_main" "check pl3_ssp of frame 2"
+	gdb_test "frame 0" "call2.*" "move to frame 0"
+	gdb_test "print /x \$pl3_ssp" "= $ssp_call2" "check pl3_ssp of frame 0"
+    }
+
+    with_test_prefix "test return from current frame" {
+	gdb_test "return (int) 1" "#0.*call1.*" \
+	"Test shadow stack return from current frame" \
+	"Make.*return now\\? \\(y or n\\) " "y"
+
+	# Potential CET violations often only occur after resuming normal execution.
+	# Therefore, it is important to test normal program continuation after
+	# testing the return command.
+	gdb_continue_to_end
+    }
+
+    clean_restart ${binfile}
+    if { ![runto_main] } {
+	return -1
+    }
+
+    with_test_prefix "test return from past frame" {
+	gdb_breakpoint $call2_line
+	gdb_continue_to_breakpoint "break call2" ".*break call2.*"
+
+	gdb_test "frame 1" ".*in call1.*"
+
+	gdb_test "return (int) 1" "#0.*main.*" \
+	    "Test shadow stack return from past frame" \
+	    "Make.*return now\\? \\(y or n\\) " "y"
+
+	# Potential CET violations often only occur after resuming normal execution.
+	# Therefore, it is important to test normal program continuation after
+	# testing the return command.
+	gdb_continue_to_end
+    }
+}
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack.c b/gdb/testsuite/gdb.arch/amd64-shadow-stack.c
index 643ef2d5f56..80389730bc2 100644
--- a/gdb/testsuite/gdb.arch/amd64-shadow-stack.c
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack.c
@@ -15,8 +15,21 @@ 
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
+static int
+call2 ()
+{
+  return 42; /* break call2.  */
+}
+
+static int
+call1 ()
+{
+  return call2 (); /* break call1.  */
+}
+
 int
 main ()
 {
+  call1 (); /* break main.  */
   return 0;
 }