gdb: Handle shadow stack pointer register unwinding for amd64 linux.

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.

Reviewed-By: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
Approved-By: Luis Machado <luis.machado@arm.com>
Approved-By: Andrew Burgess <aburgess@redhat.com>
This commit is contained in:
Christina Schimpe
2024-04-08 11:57:34 -04:00
parent e07c03e47a
commit a4011720d4
5 changed files with 240 additions and 0 deletions

View File

@@ -48,6 +48,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"
@@ -1921,6 +1923,88 @@ amd64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid,
return dtv_addr;
}
/* Return the number of bytes required to update the shadow stack pointer
by one element. For x32 the shadow stack elements are still 64-bit
aligned. Thus, gdbarch_addr_bit cannot be used to compute the new
stack pointer. */
static inline int
amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
{
const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch);
return (binfo->bits_per_word / binfo->bits_per_byte);
}
/* Implement shadow stack pointer unwinding. For each new shadow stack
pointer check if its address is still in the shadow stack memory range.
If it's outside the range set the returned value to unavailable,
otherwise return a value containing the new shadow stack pointer. */
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);
/* 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. */
CORE_ADDR new_ssp
= ssp + amd64_linux_shadow_stack_element_size_aligned (gdbarch);
/* There can be scenarios where we have a shadow stack pointer
but the shadow stack is empty, as no call instruction has
been executed yet. 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;
}
/* Implement the "init_reg" dwarf2_frame_ops method. */
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)
@@ -1978,6 +2062,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