]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb, gdbserver: Add support of Intel shadow stack pointer register.
authorChristina Schimpe <christina.schimpe@intel.com>
Fri, 29 Mar 2019 15:38:50 +0000 (16:38 +0100)
committerChristina Schimpe <christina.schimpe@intel.com>
Fri, 29 Aug 2025 17:02:09 +0000 (17:02 +0000)
This patch adds the user mode register PL3_SSP which is part of the
Intel(R) Control-Flow Enforcement Technology (CET) feature for support
of shadow stack.
For now, only native and remote debugging support for shadow stack
userspace on amd64 linux are covered by this patch including 64 bit and
x32 support.  32 bit support is not covered due to missing Linux kernel
support.

This patch requires fixing the test gdb.base/inline-frame-cycle-unwind
which is failing in case the shadow stack pointer is unavailable.
Such a state is possible if shadow stack is disabled for the current thread
but supported by HW.

This test uses the Python unwinder inline-frame-cycle-unwind.py which fakes
the cyclic stack cycle by reading the pending frame's registers and adding
them to the unwinder:

~~~
for reg in pending_frame.architecture().registers("general"):
     val = pending_frame.read_register(reg)
     unwinder.add_saved_register(reg, val)
     return unwinder
~~~

However, in case the python unwinder is used we add a register (pl3_ssp) that is
unavailable.  This leads to a NOT_AVAILABLE_ERROR caught in
gdb/frame-unwind.c:frame_unwind_try_unwinder and it is continued with standard
unwinders.  This destroys the faked cyclic behavior and the stack is
further unwinded after frame 5.

In the working scenario an error should be triggered:
~~~
bt
0  inline_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:49^M
1  normal_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:32^M
2  0x000055555555516e in inline_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:45^M
3  normal_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:32^M
4  0x000055555555516e in inline_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:45^M
5  normal_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:32^M
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) PASS: gdb.base/inline-frame-cycle-unwind.exp: cycle at level 5: backtrace when the unwind is broken at frame 5
~~~

To fix the Python unwinder, we simply skip the unavailable registers.

Also it makes the test gdb.dap/scopes.exp fail.  The shadow stack feature is
disabled by default, so the pl3_ssp register which is added with my CET
shadow stack series will be shown as unavailable and we see a TCL error:
~~
>>> {"seq": 12, "type": "request", "command": "variables", "arguments": {"variablesReference": 2, "count": 85}}
Content-Length: 129^M
^M
{"request_seq": 12, "type": "response", "command": "variables", "success": false, "message": "value is not available", "seq": 25}FAIL: gdb.dap/scopes.exp: fetch all registers success
ERROR: tcl error sourcing /tmp/gdb/testsuite/gdb.dap/scopes.exp.
ERROR: tcl error code TCL LOOKUP DICT body
ERROR: key "body" not known in dictionary
    while executing
"dict get $val body variables"
    (file "/tmp/gdb/testsuite/gdb.dap/scopes.exp" line 152)
    invoked from within
"source /tmp/gdb/testsuite/gdb.dap/scopes.exp"
    ("uplevel" body line 1)
    invoked from within
"uplevel #0 source /tmp/gdb/testsuite/gdb.dap/scopes.exp"
    invoked from within
"catch "uplevel #0 source $test_file_name" msg"
UNRESOLVED: gdb.dap/scopes.exp: testcase '/tmp/gdb/testsuite/gdb.dap/scopes.exp' aborted due to Tcl error
~~

I am fixing this by enabling the test for CET shadow stack, in case we
detect that the HW supports it:
~~~
    # If x86 shadow stack is supported we need to configure GLIBC_TUNABLES
    # such that the feature is enabled and the register pl3_ssp is
    # available.  Otherwise the reqeust to fetch all registers will fail
    # with "message": "value is not available".
    if { [allow_ssp_tests] } {
append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
    }
~~~

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Approved-By: Luis Machado <luis.machado@arm.com>
Approved-By: Andrew Burgess <aburgess@redhat.com>
30 files changed:
gdb/NEWS
gdb/amd64-linux-nat.c
gdb/amd64-linux-tdep.c
gdb/amd64-tdep.c
gdb/amd64-tdep.h
gdb/arch/amd64.c
gdb/arch/i386.c
gdb/arch/x86-linux-tdesc-features.c
gdb/doc/gdb.texinfo
gdb/features/Makefile
gdb/features/i386/32bit-ssp.c [new file with mode: 0644]
gdb/features/i386/32bit-ssp.xml [new file with mode: 0644]
gdb/features/i386/64bit-ssp.c [new file with mode: 0644]
gdb/features/i386/64bit-ssp.xml [new file with mode: 0644]
gdb/i386-tdep.c
gdb/i386-tdep.h
gdb/nat/x86-linux-tdesc.c
gdb/nat/x86-linux.c
gdb/nat/x86-linux.h
gdb/testsuite/gdb.arch/amd64-shadow-stack.c [new file with mode: 0644]
gdb/testsuite/gdb.arch/amd64-shadow-stack.exp [new file with mode: 0644]
gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
gdb/testsuite/gdb.dap/scopes.exp
gdb/testsuite/lib/gdb.exp
gdb/x86-linux-nat.c
gdb/x86-linux-nat.h
gdb/x86-tdep.c
gdb/x86-tdep.h
gdbserver/linux-x86-low.cc
gdbsupport/x86-xstate.h

index db0e74a2623f931cfbf9911ad272a42ff45ff451..416e2209bbc92b0d423a1b8e5b1409e48f9a7dcd 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 16
 
+* Support for the shadow stack pointer register on x86-64 or x86-64 with
+  32-bit pointer size (X32) GNU/Linux.
+
 * Debugger Adapter Protocol changes
 
   ** GDB now supports the "completions" request.
index dbb9b3223cbe5069ea53c82cfedebbee68a1fa36..e35527de2a6d772d6307c9315cfe5af67479fd4e 100644 (file)
@@ -32,6 +32,7 @@
 #include "amd64-tdep.h"
 #include "amd64-linux-tdep.h"
 #include "i386-linux-tdep.h"
+#include "x86-tdep.h"
 #include "gdbsupport/x86-xstate.h"
 
 #include "x86-linux-nat.h"
@@ -237,6 +238,14 @@ amd64_linux_nat_target::fetch_registers (struct regcache *regcache, int regnum)
 
       if (have_ptrace_getregset == TRIBOOL_TRUE)
        {
+         if ((regnum == -1 && tdep->ssp_regnum != -1)
+             || (regnum != -1 && regnum == tdep->ssp_regnum))
+           {
+             x86_linux_fetch_ssp (regcache, tid);
+             if (regnum != -1)
+               return;
+           }
+
          /* Pre-4.14 kernels have a bug (fixed by commit 0852b374173b
             "x86/fpu: Add FPU state copying quirk to handle XRSTOR failure on
             Intel Skylake CPUs") that sometimes causes the mxcsr location in
@@ -302,6 +311,14 @@ amd64_linux_nat_target::store_registers (struct regcache *regcache, int regnum)
       if (have_ptrace_getregset == TRIBOOL_TRUE)
        {
          gdb::byte_vector xstateregs (tdep->xsave_layout.sizeof_xsave);
+         if ((regnum == -1 && tdep->ssp_regnum != -1)
+             || (regnum != -1 && regnum == tdep->ssp_regnum))
+           {
+             x86_linux_store_ssp (regcache, tid);
+             if (regnum != -1)
+               return;
+           }
+
          struct iovec iov;
 
          iov.iov_base = xstateregs.data ();
index ce62a42f3ce497b69fb7eaac0b5cc4365f1fc6f6..f01d26754786b4212a693893cd69e8d3a1704955 100644 (file)
@@ -108,6 +108,7 @@ int amd64_linux_gregset_reg_offset[] =
   -1, -1, -1, -1, -1, -1, -1, -1,
   -1, -1, -1, -1, -1, -1, -1, -1,
   -1,                          /* PKEYS register pkru  */
+  -1,                          /* CET user mode register PL3_SSP.  */
 
   /* End of hardware registers */
   21 * 8, 22 * 8,                    /* fs_base and gs_base.  */
index 13612a0fad7995aa71127a3b55d03dde8f4e744b..e9049d31e5d50b2423afda5a64b7f23a40e9610d 100644 (file)
@@ -3412,6 +3412,9 @@ amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
       tdep->num_pkeys_regs = 1;
     }
 
+  if (tdesc_find_feature (tdesc, "org.gnu.gdb.i386.pl3_ssp") != nullptr)
+    tdep->ssp_regnum = AMD64_PL3_SSP_REGNUM;
+
   tdep->num_byte_regs = 20;
   tdep->num_word_regs = 16;
   tdep->num_dword_regs = 16;
@@ -3574,12 +3577,13 @@ const struct target_desc *
 amd64_target_description (uint64_t xstate_bv, bool segments)
 {
   static target_desc *amd64_tdescs \
-    [2/*AVX*/][2/*AVX512*/][2/*PKRU*/][2/*segments*/] = {};
+    [2/*AVX*/][2/*AVX512*/][2/*PKRU*/][2/*CET_U*/][2/*segments*/] = {};
   target_desc **tdesc;
 
   tdesc = &amd64_tdescs[(xstate_bv & X86_XSTATE_AVX) ? 1 : 0]
     [(xstate_bv & X86_XSTATE_AVX512) ? 1 : 0]
     [(xstate_bv & X86_XSTATE_PKRU) ? 1 : 0]
+    [(xstate_bv & X86_XSTATE_CET_U) ? 1 : 0]
     [segments ? 1 : 0];
 
   if (*tdesc == NULL)
index 55c308502a382a369435255b097d3b778ae9e4bc..e663288c6080da55a226ac4e1a24eeece55513f5 100644 (file)
@@ -81,6 +81,7 @@ enum amd64_regnum
   AMD64_ZMM0H_REGNUM,
   AMD64_ZMM31H_REGNUM = AMD64_ZMM0H_REGNUM + 31,
   AMD64_PKRU_REGNUM,
+  AMD64_PL3_SSP_REGNUM,
   AMD64_FSBASE_REGNUM,
   AMD64_GSBASE_REGNUM
 };
index e16652ac04513d3916d5a9da1ba66ca50802bf0e..9fbe80266005801c6951ef7506e3cdd69d98d0b3 100644 (file)
@@ -28,6 +28,8 @@
 #include "../features/i386/64bit-sse.c"
 #include "../features/i386/pkeys.c"
 
+#include "../features/i386/64bit-ssp.c"
+#include "../features/i386/32bit-ssp.c"
 #include "../features/i386/x32-core.c"
 
 /* See arch/amd64.h.  */
@@ -68,5 +70,13 @@ amd64_create_target_description (uint64_t xstate_bv, bool is_x32,
   if (xstate_bv & X86_XSTATE_PKRU)
     regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
 
+  if (xstate_bv & X86_XSTATE_CET_U)
+    {
+      if (!is_x32)
+       regnum = create_feature_i386_64bit_ssp (tdesc.get (), regnum);
+      else
+       regnum = create_feature_i386_32bit_ssp (tdesc.get (), regnum);
+    }
+
   return tdesc.release ();
 }
index 424bd27b4ec18315cd437422a00d045380fc5540..4ec4f10311187ae3fa6a2a1e58a645a2a79d2bfd 100644 (file)
@@ -28,6 +28,7 @@
 #include "../features/i386/32bit-avx512.c"
 #include "../features/i386/32bit-segments.c"
 #include "../features/i386/pkeys.c"
+#include "../features/i386/32bit-ssp.c"
 
 /* See arch/i386.h.  */
 
@@ -66,5 +67,8 @@ i386_create_target_description (uint64_t xstate_bv, bool is_linux,
   if (xstate_bv & X86_XSTATE_PKRU)
     regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
 
+  if (xstate_bv & X86_XSTATE_CET_U)
+    regnum = create_feature_i386_32bit_ssp (tdesc.get (), regnum);
+
   return tdesc.release ();
 }
index 3863d1f30edc5a0a4ad93fe37cca7840985dbbf7..bc343786aa1dce1b7def2487598095f8199441f3 100644 (file)
@@ -65,6 +65,7 @@ struct x86_xstate_feature {
 
 static constexpr x86_xstate_feature x86_linux_all_xstate_features[] = {
   /* Feature,           i386,  amd64,  x32.  */
+  { X86_XSTATE_CET_U,  false,  true,   true },
   { X86_XSTATE_PKRU,   true,   true,   true },
   { X86_XSTATE_AVX512, true,   true,   true },
   { X86_XSTATE_AVX,    true,   true,   true },
index e9376eb579fa87467eb91771b12b5787e6ccc4b8..4c3b7f2a010f5813074cf0e561c82b44adfaf85f 100644 (file)
@@ -50037,6 +50037,12 @@ The @samp{org.gnu.gdb.i386.pkeys} feature is optional.  It should
 describe a single register, @samp{pkru}.  It is a 32-bit register
 valid for i386 and amd64.
 
+The @samp{org.gnu.gdb.i386.pl3_ssp} feature is optional.  It should
+describe the user mode register @samp{pl3_ssp} which has 64 bits on
+amd64, 32 bits on amd64 with 32-bit pointer size (X32) and 32 bits on i386.
+Following the restriction of the Linux kernel, only @value{GDBN} for amd64
+targets makes use of this feature for now.
+
 @node LoongArch Features
 @subsection LoongArch Features
 @cindex target descriptions, LoongArch Features
index 750508a85e6d33e308cc14f1e2a29f2827a4b363..b206ddd53031478f319ff7b8020c669ccec20cf8 100644 (file)
@@ -226,6 +226,7 @@ FEATURE_XMLFILES = aarch64-core.xml \
        i386/32bit-avx.xml \
        i386/32bit-avx512.xml \
        i386/32bit-segments.xml \
+       i386/32bit-ssp.xml \
        i386/64bit-avx512.xml \
        i386/64bit-core.xml \
        i386/64bit-segments.xml \
@@ -233,6 +234,7 @@ FEATURE_XMLFILES = aarch64-core.xml \
        i386/64bit-linux.xml \
        i386/64bit-sse.xml \
        i386/pkeys.xml \
+       i386/64bit-ssp.xml \
        i386/x32-core.xml \
        loongarch/base32.xml \
        loongarch/base64.xml \
diff --git a/gdb/features/i386/32bit-ssp.c b/gdb/features/i386/32bit-ssp.c
new file mode 100644 (file)
index 0000000..991bae3
--- /dev/null
@@ -0,0 +1,14 @@
+/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
+  Original: 32bit-ssp.xml */
+
+#include "gdbsupport/tdesc.h"
+
+static int
+create_feature_i386_32bit_ssp (struct target_desc *result, long regnum)
+{
+  struct tdesc_feature *feature;
+
+  feature = tdesc_create_feature (result, "org.gnu.gdb.i386.pl3_ssp");
+  tdesc_create_reg (feature, "pl3_ssp", regnum++, 1, NULL, 32, "data_ptr");
+  return regnum;
+}
diff --git a/gdb/features/i386/32bit-ssp.xml b/gdb/features/i386/32bit-ssp.xml
new file mode 100644 (file)
index 0000000..d17e700
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!-- Copyright (C) 2022-2024 Free Software Foundation, Inc.
+
+     Copying and distribution of this file, with or without modification,
+     are permitted in any medium without royalty provided the copyright
+     notice and this notice are preserved.  -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.i386.pl3_ssp">
+  <reg name="pl3_ssp" bitsize="32" type="data_ptr"/>
+</feature>
diff --git a/gdb/features/i386/64bit-ssp.c b/gdb/features/i386/64bit-ssp.c
new file mode 100644 (file)
index 0000000..5468099
--- /dev/null
@@ -0,0 +1,14 @@
+/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
+  Original: 64bit-ssp.xml */
+
+#include "gdbsupport/tdesc.h"
+
+static int
+create_feature_i386_64bit_ssp (struct target_desc *result, long regnum)
+{
+  struct tdesc_feature *feature;
+
+  feature = tdesc_create_feature (result, "org.gnu.gdb.i386.pl3_ssp");
+  tdesc_create_reg (feature, "pl3_ssp", regnum++, 1, NULL, 64, "data_ptr");
+  return regnum;
+}
diff --git a/gdb/features/i386/64bit-ssp.xml b/gdb/features/i386/64bit-ssp.xml
new file mode 100644 (file)
index 0000000..a0688d0
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!-- Copyright (C) 2022-2024 Free Software Foundation, Inc.
+
+     Copying and distribution of this file, with or without modification,
+     are permitted in any medium without royalty provided the copyright
+     notice and this notice are preserved.  -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.i386.pl3_ssp">
+  <reg name="pl3_ssp" bitsize="64" type="data_ptr"/>
+</feature>
index 4715ade61f11ef660a8576a7aa2889a2f7cec474..8a053315ea8873a3f20ebe8ed61e37041bc3e467 100644 (file)
@@ -8580,7 +8580,8 @@ i386_validate_tdesc_p (i386_gdbarch_tdep *tdep,
   const struct tdesc_feature *feature_core;
 
   const struct tdesc_feature *feature_sse, *feature_avx, *feature_avx512,
-                            *feature_pkeys, *feature_segments;
+                            *feature_pkeys, *feature_segments,
+                            *feature_pl3_ssp;
   int i, num_regs, valid_p;
 
   if (! tdesc_has_registers (tdesc))
@@ -8606,6 +8607,9 @@ i386_validate_tdesc_p (i386_gdbarch_tdep *tdep,
   /* Try PKEYS  */
   feature_pkeys = tdesc_find_feature (tdesc, "org.gnu.gdb.i386.pkeys");
 
+  /* Try Shadow Stack.  */
+  feature_pl3_ssp = tdesc_find_feature (tdesc, "org.gnu.gdb.i386.pl3_ssp");
+
   valid_p = 1;
 
   /* The XCR0 bits.  */
@@ -8721,6 +8725,15 @@ i386_validate_tdesc_p (i386_gdbarch_tdep *tdep,
                                            tdep->pkeys_register_names[i]);
     }
 
+  if (feature_pl3_ssp != nullptr)
+    {
+      if (tdep->ssp_regnum < 0)
+       tdep->ssp_regnum = I386_PL3_SSP_REGNUM;
+
+      valid_p &= tdesc_numbered_register (feature_pl3_ssp, tdesc_data,
+                                         tdep->ssp_regnum, "pl3_ssp");
+    }
+
   return valid_p;
 }
 
@@ -9103,13 +9116,15 @@ const struct target_desc *
 i386_target_description (uint64_t xstate_bv, bool segments)
 {
   static target_desc *i386_tdescs \
-    [2/*SSE*/][2/*AVX*/][2/*AVX512*/][2/*PKRU*/][2/*segments*/] = {};
+    [2/*SSE*/][2/*AVX*/][2/*AVX512*/][2/*PKRU*/][2/*CET_U*/] \
+    [2/*segments*/] = {};
   target_desc **tdesc;
 
   tdesc = &i386_tdescs[(xstate_bv & X86_XSTATE_SSE) ? 1 : 0]
     [(xstate_bv & X86_XSTATE_AVX) ? 1 : 0]
     [(xstate_bv & X86_XSTATE_AVX512) ? 1 : 0]
     [(xstate_bv & X86_XSTATE_PKRU) ? 1 : 0]
+    [(xstate_bv & X86_XSTATE_CET_U) ? 1 : 0]
     [segments ? 1 : 0];
 
   if (*tdesc == NULL)
index 65095939d0b63d297381e2cc2ef18972cb3e577f..84f1bbde0075c385f70d7a3798e2afe0784050f4 100644 (file)
@@ -195,6 +195,10 @@ struct i386_gdbarch_tdep : gdbarch_tdep_base
   /* PKEYS register names.  */
   const char * const *pkeys_register_names = nullptr;
 
+  /* Register number for the shadow stack pointer register.  If supported,
+     set this to a value >= 0.  */
+  int ssp_regnum = -1;
+
   /* Register number for %fsbase.  If supported, set this to a value
      >= 0.  */
   int fsbase_regnum = -1;
@@ -297,6 +301,7 @@ enum i386_regnum
   I386_ZMM0H_REGNUM,           /* %zmm0h */
   I386_ZMM7H_REGNUM = I386_ZMM0H_REGNUM + 7,
   I386_PKRU_REGNUM,
+  I386_PL3_SSP_REGNUM,
   I386_FSBASE_REGNUM,
   I386_GSBASE_REGNUM
 };
index e9cf2527c5f19fbae407e2d8d4baeb1fbe8ff665..5bc36b6bef21c15f20dc50510fe551b3751ef754 100644 (file)
@@ -110,6 +110,8 @@ x86_linux_tdesc_for_tid (int tid, uint64_t *xstate_bv_storage,
            = x86_fetch_xsave_layout (xcr0, x86_xsave_length ());
 
          *xstate_bv_storage = xcr0;
+         if (x86_check_ssp_support (tid))
+           *xstate_bv_storage |= X86_XSTATE_CET_U;
        }
     }
 
index 0bdff736f8ab2e21842037f833453cf221a6c1af..55158268970469068760c82f7fbbb1e1efa18f97 100644 (file)
    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 "elf/common.h"
+#include "gdbsupport/common-defs.h"
+#include "nat/gdb_ptrace.h"
+#include "nat/linux-ptrace.h"
+#include "nat/x86-cpuid.h"
+#include <sys/uio.h>
 #include "x86-linux.h"
 #include "x86-linux-dregs.h"
 #include "nat/gdb_ptrace.h"
@@ -126,3 +132,56 @@ x86_linux_ptrace_get_arch_size (int tid)
   return x86_linux_arch_size (false, false);
 #endif
 }
+
+/* See nat/x86-linux.h.  */
+
+bool
+x86_check_ssp_support (const int tid)
+{
+  /* It's not enough to check shadow stack support with the ptrace call
+     below only, as we cannot distinguish between shadow stack not enabled
+     for the current thread and shadow stack is not supported by HW.  In
+     both scenarios the ptrace call fails with ENODEV.  In case shadow
+     stack is not enabled for the current thread, we still want to return
+     true.  */
+  unsigned int eax, ebx, ecx, edx;
+  eax = ebx = ecx = edx = 0;
+
+  if (!__get_cpuid_count (7, 0, &eax, &ebx, &ecx, &edx))
+    return false;
+
+  if ((ecx & bit_SHSTK) == 0)
+    return false;
+
+  /* Further check for NT_X86_SHSTK kernel support.  */
+  uint64_t ssp;
+  iovec iov {&ssp, sizeof (ssp) };
+
+  errno = 0;
+  int res = ptrace (PTRACE_GETREGSET, tid, NT_X86_SHSTK, &iov);
+  if (res < 0)
+    {
+      if (errno == EINVAL)
+       {
+         /* The errno EINVAL for a PTRACE_GETREGSET call indicates that
+            kernel support is not available.  */
+         return false;
+       }
+      else if (errno == ENODEV)
+       {
+         /* At this point, since we already checked CPUID, the errno
+            ENODEV for a PTRACE_GETREGSET call indicates that shadow
+            stack is not enabled for the current thread.  As it could be
+            enabled later, we still want to return true here.  */
+         return true;
+       }
+      else
+       {
+         warning (_("Unknown ptrace error for NT_X86_SHSTK: %s"),
+                  safe_strerror (errno));
+         return false;
+       }
+    }
+
+  return true;
+}
index dbdef0815151369be67b779f89111ef62a4b8b95..1783aae05d03549f39cb0c4e57c052e14db6a04a 100644 (file)
@@ -75,4 +75,8 @@ private:
 
 extern x86_linux_arch_size x86_linux_ptrace_get_arch_size (int tid);
 
+/* Check shadow stack hardware and kernel support.  */
+
+extern bool x86_check_ssp_support (const int tid);
+
 #endif /* GDB_NAT_X86_LINUX_H */
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack.c b/gdb/testsuite/gdb.arch/amd64-shadow-stack.c
new file mode 100644 (file)
index 0000000..be00447
--- /dev/null
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2024-2025 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/>.  */
+
+int
+main ()
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack.exp
new file mode 100644 (file)
index 0000000..a72334a
--- /dev/null
@@ -0,0 +1,71 @@
+# Copyright 2024-2025 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 accessing the shadow stack pointer register.
+
+require allow_ssp_tests
+
+standard_testfile
+
+# Write PL3_SSP register with invalid shadow stack pointer value.
+proc write_invalid_ssp {} {
+    gdb_test "print /x \$pl3_ssp = 0x12345678" "= 0x12345678" "set pl3_ssp value"
+    gdb_test "print /x \$pl3_ssp" "= 0x12345678" "read pl3_ssp value after setting"
+}
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+
+    append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+    if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+         additional_flags="-fcf-protection=return"] } {
+       return
+    }
+
+    if {![runto_main]} {
+       return
+    }
+
+    with_test_prefix "invalid ssp" {
+       write_invalid_ssp
+
+       # Continue until SIGSEV to test that the value is written back to HW.
+       gdb_test "continue" \
+           [multi_line \
+               "Continuing\\." \
+               "" \
+               "Program received signal SIGSEGV, Segmentation fault\\." \
+               "$hex in main \\(\\)"] \
+           "continue to SIGSEGV"
+    }
+
+    clean_restart ${binfile}
+    if { ![runto_main] } {
+       return
+    }
+
+    with_test_prefix "restore original ssp" {
+       # Read PL3_SSP register.
+       set ssp_main [get_hexadecimal_valueof "\$pl3_ssp" "read pl3_ssp value"]
+
+       write_invalid_ssp
+
+       # Restore original value.
+       gdb_test "print /x \$pl3_ssp = $ssp_main" "= $ssp_main" "restore original value"
+
+       # Now we should not see a SIGSEV, since the original value is restored.
+       gdb_continue_to_end
+    }
+}
index bc4a673b84706b4258cbbe9c14cb568438bc79b2..654ff447523dc4999eae95d53565a4843b0d6c5d 100644 (file)
@@ -65,6 +65,10 @@ class TestUnwinder(Unwinder):
 
         for reg in pending_frame.architecture().registers("general"):
             val = pending_frame.read_register(reg)
+            # Having unavailable registers leads to a fall back to the standard
+            # unwinders.  Don't add unavailable registers to avoid this.
+            if (str (val) == "<unavailable>"):
+                continue
             unwinder.add_saved_register(reg, val)
         return unwinder
 
index 52efa683c4ceb29d9b7037c4eccc6be63ab869c9..e4e5c28d9c1b4132628963aaa730e089761128cf 100644 (file)
@@ -25,155 +25,166 @@ if {[build_executable ${testfile}.exp $testfile] == -1} {
     return
 }
 
-if {[dap_initialize] == ""} {
-    return
-}
+save_vars { ::env(GLIBC_TUNABLES) } {
+
+    # If x86 shadow stack is supported we need to configure GLIBC_TUNABLES
+    # such that the feature is enabled and the register pl3_ssp is
+    # available.  Otherwise the request to fetch all registers will fail
+    # with "message": "value is not available".
+    if { [allow_ssp_tests] } {
+       append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+    }
+
+    if {[dap_initialize] == ""} {
+       return
+    }
 
-set launch_id [dap_launch $testfile]
+    set launch_id [dap_launch $testfile]
 
-set line [gdb_get_line_number "BREAK"]
-set obj [dap_check_request_and_response "set breakpoint by line number" \
-            setBreakpoints \
-            [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \
-                 [list s $srcfile] $line]]
-set line_bpno [dap_get_breakpoint_number $obj]
+    set line [gdb_get_line_number "BREAK"]
+    set obj [dap_check_request_and_response "set breakpoint by line number" \
+               setBreakpoints \
+               [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \
+                     [list s $srcfile] $line]]
+    set line_bpno [dap_get_breakpoint_number $obj]
 
-dap_check_request_and_response "configurationDone" configurationDone
+    dap_check_request_and_response "configurationDone" configurationDone
 
-dap_check_response "launch response" launch $launch_id
+    dap_check_response "launch response" launch $launch_id
 
-dap_wait_for_event_and_check "inferior started" thread "body reason" started
+    dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
-dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
-    "body reason" breakpoint \
-    "body hitBreakpointIds" $line_bpno
+    dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
+       "body reason" breakpoint \
+       "body hitBreakpointIds" $line_bpno
 
-set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
-                   {o threadId [i 1]}] \
-           0]
-set frame_id [dict get [lindex [dict get $bt body stackFrames] 0] id]
+    set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
+                       {o threadId [i 1]}] \
+               0]
+    set frame_id [dict get [lindex [dict get $bt body stackFrames] 0] id]
 
-set scopes [dap_check_request_and_response "get scopes" scopes \
+    set scopes [dap_check_request_and_response "get scopes" scopes \
                [format {o frameId [i %d]} $frame_id]]
-set scopes [dict get [lindex $scopes 0] body scopes]
+    set scopes [dict get [lindex $scopes 0] body scopes]
 
-# Request the scopes twice, and verify that the results are identical.
-# GDB previously had a bug where it would return new scopes each time.
-set scopes2 [dap_check_request_and_response "get scopes again" scopes \
+    # Request the scopes twice, and verify that the results are identical.
+    # GDB previously had a bug where it would return new scopes each time.
+    set scopes2 [dap_check_request_and_response "get scopes again" scopes \
                 [format {o frameId [i %d]} $frame_id]]
-set scopes2 [dict get [lindex $scopes2 0] body scopes]
-gdb_assert {$scopes2 == $scopes} "identical scopes requests yield same body"
-
-gdb_assert {[llength $scopes] == 2} "two scopes"
-
-lassign $scopes scope reg_scope
-gdb_assert {[dict get $scope name] == "Locals"} "scope is locals"
-gdb_assert {[dict get $scope presentationHint] == "locals"} \
-    "locals presentation hint"
-set count [dict get $scope namedVariables]
-gdb_assert {$count == 4} "four vars in scope"
-
-gdb_assert {[dict get $reg_scope name] == "Registers"} \
-    "second scope is registers"
-gdb_assert {[dict get $reg_scope presentationHint] == "registers"} \
-    "registers presentation hint"
-gdb_assert {[dict get $reg_scope namedVariables] > 0} "at least one register"
-
-set num [dict get $scope variablesReference]
-# Send two requests and combine them, to verify that using a range
-# works.
-set refs1 [lindex [dap_check_request_and_response "fetch variables 0,1" \
-                      "variables" \
-                      [format {o variablesReference [i %d] count [i 2]} \
-                           $num]] \
-              0]
-set refs2 [lindex [dap_check_request_and_response "fetch variables 2" \
-                      "variables" \
-                      [format {o variablesReference [i %d] \
-                                   start [i 2] count [i %d]} \
-                           $num [expr {$count - 2}]]] \
-              0]
-
-set vars [concat [dict get $refs1 body variables] \
-             [dict get $refs2 body variables]]
-foreach var $vars {
-    set name [dict get $var name]
-
-    if {$name != "dei"} {
-       gdb_assert {[dict get $var variablesReference] == 0} \
-           "$name has no structure"
-    }
-
-    switch $name {
-       "inner" {
-           gdb_assert {[string match "*inner block*" [dict get $var value]]} \
-               "check value of inner"
-       }
-       "dei" {
-           gdb_assert {[dict get $var value] == ""} "check value of dei"
-           set dei_ref [dict get $var variablesReference]
+    set scopes2 [dict get [lindex $scopes2 0] body scopes]
+    gdb_assert {$scopes2 == $scopes} "identical scopes requests yield same body"
+
+    gdb_assert {[llength $scopes] == 2} "two scopes"
+
+    lassign $scopes scope reg_scope
+    gdb_assert {[dict get $scope name] == "Locals"} "scope is locals"
+    gdb_assert {[dict get $scope presentationHint] == "locals"} \
+       "locals presentation hint"
+    set count [dict get $scope namedVariables]
+    gdb_assert {$count == 4} "four vars in scope"
+
+    gdb_assert {[dict get $reg_scope name] == "Registers"} \
+       "second scope is registers"
+    gdb_assert {[dict get $reg_scope presentationHint] == "registers"} \
+       "registers presentation hint"
+    gdb_assert {[dict get $reg_scope namedVariables] > 0} "at least one register"
+
+    set num [dict get $scope variablesReference]
+    # Send two requests and combine them, to verify that using a range
+    # works.
+    set refs1 [lindex [dap_check_request_and_response "fetch variables 0,1" \
+                          "variables" \
+                          [format {o variablesReference [i %d] count [i 2]} \
+                               $num]] \
+                  0]
+    set refs2 [lindex [dap_check_request_and_response "fetch variables 2" \
+                          "variables" \
+                          [format {o variablesReference [i %d] \
+                                       start [i 2] count [i %d]} \
+                               $num [expr {$count - 2}]]] \
+                  0]
+
+    set vars [concat [dict get $refs1 body variables] \
+                  [dict get $refs2 body variables]]
+    foreach var $vars {
+       set name [dict get $var name]
+
+       if {$name != "dei"} {
+           gdb_assert {[dict get $var variablesReference] == 0} \
+               "$name has no structure"
        }
-       "scalar" {
-           gdb_assert {[dict get $var value] == 23} "check value of scalar"
-       }
-       "ptr" {
-           gdb_assert {[dict get $var memoryReference] != ""} \
-               "check memoryReference of ptr"
-       }
-       default {
-           fail "unknown variable $name"
+
+       switch $name {
+           "inner" {
+               gdb_assert {[string match "*inner block*" [dict get $var value]]} \
+                   "check value of inner"
+           }
+           "dei" {
+               gdb_assert {[dict get $var value] == ""} "check value of dei"
+               set dei_ref [dict get $var variablesReference]
+           }
+           "scalar" {
+               gdb_assert {[dict get $var value] == 23} "check value of scalar"
+           }
+           "ptr" {
+               gdb_assert {[dict get $var memoryReference] != ""} \
+                   "check memoryReference of ptr"
+           }
+           default {
+               fail "unknown variable $name"
+           }
        }
     }
-}
 
-set refs [lindex [dap_check_request_and_response "fetch contents of dei" \
-                     "variables" \
-                     [format {o variablesReference [i %d]} $dei_ref]] \
-             0]
-set deivals [dict get $refs body variables]
-gdb_assert {[llength $deivals] == 2} "dei has two members"
-
-# Request more children than exist.  See PR dap/33228.
-set seq [dap_send_request variables \
-            [format {o variablesReference [i %d] count [i 100]} $dei_ref]]
-lassign [dap_read_response variables $seq] response ignore
-gdb_assert {[dict get $response success] == "false"} \
-    "variables with invalid count"
-
-set num [dict get $reg_scope variablesReference]
-lassign [dap_check_request_and_response "fetch all registers" \
-            "variables" \
-            [format {o variablesReference [i %d] count [i %d]} $num\
-                 [dict get $reg_scope namedVariables]]] \
-    val events
-
-# If any register has children, try to fetch those as well.  This is a
-# regression test for part of PR dap/33228.
-foreach var [dict get $val body variables] {
-    set regvar [dict get $var variablesReference]
-    if {$regvar > 0} {
-       # If variablesReference is non-zero, then there must be either
-       # named or indexed children.
-       if {[dict exists $var namedVariables]} {
-           set n [dict get $var namedVariables]
-       } else {
-           set n [dict get $var indexedVariables]
+    set refs [lindex [dap_check_request_and_response "fetch contents of dei" \
+                          "variables" \
+                          [format {o variablesReference [i %d]} $dei_ref]] \
+                  0]
+    set deivals [dict get $refs body variables]
+    gdb_assert {[llength $deivals] == 2} "dei has two members"
+
+    # Request more children than exist.  See PR dap/33228.
+    set seq [dap_send_request variables \
+                  [format {o variablesReference [i %d] count [i 100]} $dei_ref]]
+    lassign [dap_read_response variables $seq] response ignore
+    gdb_assert {[dict get $response success] == "false"} \
+       "variables with invalid count"
+
+    set num [dict get $reg_scope variablesReference]
+    lassign [dap_check_request_and_response "fetch all registers" \
+                "variables" \
+                [format {o variablesReference [i %d] count [i %d]} $num\
+                     [dict get $reg_scope namedVariables]]] \
+       val events
+
+    # If any register has children, try to fetch those as well.  This is a
+    # regression test for part of PR dap/33228.
+    foreach var [dict get $val body variables] {
+       set regvar [dict get $var variablesReference]
+       if {$regvar > 0} {
+           # If variablesReference is non-zero, then there must be either
+           # named or indexed children.
+           if {[dict exists $var namedVariables]} {
+               set n [dict get $var namedVariables]
+           } else {
+               set n [dict get $var indexedVariables]
+           }
+
+           dap_check_request_and_response "fetch register children for $regvar" \
+               "variables" \
+               [format {o variablesReference [i %d] count [i %d]} $regvar $n]
        }
-
-       dap_check_request_and_response "fetch register children for $regvar" \
-           "variables" \
-           [format {o variablesReference [i %d] count [i %d]} $regvar $n]
     }
-}
 
-set num [dict get $scope variablesReference]
-set refs [lindex [dap_check_request_and_response "set variable scalar" \
-                     "setVariable" \
-                     [format {o variablesReference [i %d] name [s scalar] \
-                                  value [s 32]} \
+    set num [dict get $scope variablesReference]
+    set refs [lindex [dap_check_request_and_response "set variable scalar" \
+                         "setVariable" \
+                         [format {o variablesReference [i %d] name [s scalar] \
+                          value [s 32]} \
                           $num]] \
-             0]
-gdb_assert { [dict get $refs body value] == 32 } \
-    "setting variable yields updated value"
+                 0]
+    gdb_assert { [dict get $refs body value] == 32 } \
+       "setting variable yields updated value"
 
-dap_shutdown
+    dap_shutdown
+}
index 4e28c1ba6b2a608f95fb32a71eb702b057d81fb1..203955d9eaab96bbf799ad30c9a3ddf994bb91bf 100644 (file)
@@ -4466,6 +4466,76 @@ gdb_caching_proc allow_tsx_tests {} {
     return $allow_tsx_tests
 }
 
+# Run a test on the target to check if it supports x86 shadow stack.  Return 1
+# if shadow stack is enabled, 0 otherwise.
+
+gdb_caching_proc allow_ssp_tests {} {
+    global srcdir subdir gdb_prompt hex
+
+    set me "allow_ssp_tests"
+
+    if { ![istarget i?86-*-*] && ![istarget x86_64-*-* ] } {
+       verbose "$me: target known to not support shadow stack."
+       return 0
+    }
+
+    # There is no need to check the actual HW in addition to ptrace support.
+    # We need both checks and ptrace will tell us about the HW state.
+    set compile_flags "{additional_flags=-fcf-protection=return}"
+    set src { int main() { return 0; } }
+    if {![gdb_simple_compile $me $src executable $compile_flags]} {
+       return 0
+    }
+
+    save_vars { ::env(GLIBC_TUNABLES) } {
+
+       append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+       # No error message, compilation succeeded so now run it via gdb.
+       gdb_exit
+       gdb_start
+       gdb_reinitialize_dir $srcdir/$subdir
+       gdb_load $obj
+       if {![runto_main]} {
+           remote_file build delete $obj
+           return 0
+       }
+       set shadow_stack_disabled_re "(<unavailable>)"
+       if {[istarget *-*-linux*]} {
+           # Starting with v6.6, the Linux kernel supports CET shadow stack.
+           # Dependent on the target we can see a nullptr or "<unavailable>"
+           # when shadow stack is supported by HW and the Linux kernel but
+           # not enabled for the current thread (for example due to a lack
+           # of compiler or glibc support for -fcf-protection).
+           set shadow_stack_disabled_re "$shadow_stack_disabled_re|(.*0x0)"
+       }
+
+       set allow_ssp_tests 0
+       gdb_test_multiple "print \$pl3_ssp" "test shadow stack support" {
+           -re -wrap "(.*$hex)((?!(.*0x0)).)" {
+               verbose -log "$me: Shadow stack support detected."
+               set allow_ssp_tests 1
+           }
+           -re -wrap $shadow_stack_disabled_re {
+               # In case shadow stack is not enabled (for example due to a
+               # lack of compiler or glibc support for -fcf-protection).
+               verbose -log "$me: Shadow stack is not enabled."
+           }
+           -re -wrap "void" {
+               # In case we don't have hardware or kernel support.
+               verbose -log "$me: No shadow stack support."
+           }
+       }
+
+       gdb_exit
+    }
+
+    remote_file build delete $obj
+
+    verbose "$me: returning $allow_ssp_tests" 2
+    return $allow_ssp_tests
+}
+
 # Run a test on the target to see if it supports avx512bf16.  Return 1 if so,
 # 0 if it does not.  Based on 'check_vmx_hw_available' from the GCC testsuite.
 
index 1b7dd8506dd3f7c93af3d198937859a35ecce8b5..660a906cdb6ff9f17d27c5b6c94e5e57e6f571b7 100644 (file)
@@ -41,6 +41,7 @@
 #include "nat/x86-linux.h"
 #include "nat/x86-linux-dregs.h"
 #include "nat/linux-ptrace.h"
+#include "x86-tdep.h"
 #include "nat/x86-linux-tdesc.h"
 
 /* linux_nat_target::low_new_fork implementation.  */
@@ -97,11 +98,10 @@ const struct target_desc *
 x86_linux_nat_target::read_description ()
 {
   /* The x86_linux_tdesc_for_tid call only reads xcr0 the first time it is
-     called.  The mask is stored in XSTATE_BV_STORAGE and reused on
-     subsequent calls.  Note that GDB currently supports features for user
-     state components only.  However, once supervisor state components are
-     supported in GDB, the value XSTATE_BV_STORAGE will not be configured
-     based on xcr0 only.  */
+     called.  Also it checks the enablement state of features which are
+     not configured in xcr0, such as CET shadow stack.  Once the supported
+     features are identified, the XSTATE_BV_STORAGE value is configured
+     accordingly and preserved for subsequent calls of this function.  */
   static uint64_t xstate_bv_storage;
 
   if (inferior_ptid == null_ptid)
@@ -215,6 +215,45 @@ x86_linux_get_thread_area (pid_t pid, void *addr, unsigned int *base_addr)
 }
 \f
 
+/* See x86-linux-nat.h.  */
+
+void
+x86_linux_fetch_ssp (regcache *regcache, const int tid)
+{
+  uint64_t ssp = 0x0;
+  iovec iov {&ssp, sizeof (ssp)};
+
+  /* The shadow stack may be enabled and disabled at runtime.  Reading the
+     ssp might fail as shadow stack was not activated for the current
+     thread.  We don't want to show a warning but silently return.  The
+     register will be shown as unavailable for the user.  */
+  if (ptrace (PTRACE_GETREGSET, tid, NT_X86_SHSTK, &iov) != 0)
+    return;
+
+  x86_supply_ssp (regcache, ssp);
+}
+
+/* See x86-linux-nat.h.  */
+
+void
+x86_linux_store_ssp (const regcache *regcache, const int tid)
+{
+  uint64_t ssp = 0x0;
+  iovec iov {&ssp, sizeof (ssp)};
+  x86_collect_ssp (regcache, ssp);
+
+  /* Dependent on the target the ssp register can be unavailable or
+     nullptr when shadow stack is supported by HW and the Linux kernel but
+     not enabled for the current thread.  In case of nullptr, GDB tries to
+     restore the shadow stack pointer after an inferior call.  The ptrace
+     call with PTRACE_SETREGSET will fail here with errno ENODEV.  We
+     don't want to throw an error in this case but silently continue.  */
+  errno = 0;
+  if ((ptrace (PTRACE_SETREGSET, tid, NT_X86_SHSTK, &iov) != 0)
+      && (errno != ENODEV))
+    perror_with_name (_("Failed to write pl3_ssp register"));
+}
+
 INIT_GDB_FILE (x86_linux_nat)
 {
   /* Initialize the debug register function vectors.  */
index a62cc4d7ac0abc14134970ca7fa0a5830b8f49c0..c4556532226363ec832a9d7b45d8b5e05271b597 100644 (file)
@@ -92,4 +92,15 @@ private:
 extern ps_err_e x86_linux_get_thread_area (pid_t pid, void *addr,
                                           unsigned int *base_addr);
 
+/* Fetch the value of the shadow stack pointer register from process/thread
+   TID and store it to GDB's register cache.  */
+
+extern void x86_linux_fetch_ssp (regcache *regcache, const int tid);
+
+/* Read the value of the shadow stack pointer from GDB's register cache
+   and store it in the shadow stack pointer register of process/thread TID.
+   Throw an error in case of failure.  */
+
+extern void x86_linux_store_ssp (const regcache *regcache, const int tid);
+
 #endif /* GDB_X86_LINUX_NAT_H */
index 6646b1157c8402b57fb3a8cc29a6a376d06541f9..ea5226f1a24dad55206d4ef8b1148aac76cd3a4a 100644 (file)
    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 "i386-tdep.h"
 #include "x86-tdep.h"
 #include "symtab.h"
 
 
+/* See x86-tdep.h.  */
+
+void
+x86_supply_ssp (regcache *regcache, const uint64_t ssp)
+{
+  i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (regcache->arch ());
+  gdb_assert (tdep != nullptr && tdep->ssp_regnum != -1);
+  regcache->raw_supply (tdep->ssp_regnum, &ssp);
+}
+
+/* See x86-tdep.h.  */
+
+void
+x86_collect_ssp (const regcache *regcache, uint64_t &ssp)
+{
+  i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (regcache->arch ());
+  gdb_assert (tdep != nullptr && tdep->ssp_regnum != -1);
+  regcache->raw_collect (tdep->ssp_regnum, &ssp);
+}
+
 /* Check whether NAME is included in NAMES[LO] (inclusive) to NAMES[HI]
    (exclusive).  */
 
index 35e3905f2e31646650c5d519573161c3fc70bea3..855d04bb23a7339a755131cbcafd1c95c04ef1a5 100644 (file)
 #ifndef GDB_X86_TDEP_H
 #define GDB_X86_TDEP_H
 
+/* Fill SSP to the shadow stack pointer in GDB's REGCACHE.  */
+
+extern void x86_supply_ssp (regcache *regcache, const uint64_t ssp);
+
+/* Collect the value of the shadow stack pointer in GDB's REGCACHE and
+   write it to SSP.  */
+
+extern void x86_collect_ssp (const regcache *regcache, uint64_t &ssp);
+
 /* Checks whether PC lies in an indirect branch thunk using registers
    REGISTER_NAMES[LO] (inclusive) to REGISTER_NAMES[HI] (exclusive).  */
 
index 57661344921cd8fc065444496ca58638896882b6..44257d5d1ceb9b489464cf3d4d585fa9d1593235 100644 (file)
@@ -253,7 +253,8 @@ static const int x86_64_regmap[] =
   -1, -1, -1, -1, -1, -1, -1, -1,
   -1, -1, -1, -1, -1, -1, -1, -1,
   -1, -1, -1, -1, -1, -1, -1, -1,
-  -1                                   /* pkru  */
+  -1,                                  /* pkru  */
+  -1                                   /* CET user mode register PL3_SSP.  */
 };
 
 #define X86_64_NUM_REGS (sizeof (x86_64_regmap) / sizeof (x86_64_regmap[0]))
@@ -405,6 +406,18 @@ x86_target::low_cannot_fetch_register (int regno)
   return regno >= I386_NUM_REGS;
 }
 
+static void
+x86_fill_ssp_reg (regcache *regcache, void *buf)
+{
+  collect_register_by_name (regcache, "pl3_ssp", buf);
+}
+
+static void
+x86_store_ssp_reg (regcache *regcache, const void *buf)
+{
+  supply_register_by_name (regcache, "pl3_ssp", buf);
+}
+
 static void
 collect_register_i386 (struct regcache *regcache, int regno, void *buf)
 {
@@ -544,6 +557,8 @@ static struct regset_info x86_regsets[] =
     x86_fill_gregset, x86_store_gregset },
   { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_X86_XSTATE, 0,
     EXTENDED_REGS, x86_fill_xstateregset, x86_store_xstateregset },
+  { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_X86_SHSTK, 0,
+    OPTIONAL_RUNTIME_REGS, x86_fill_ssp_reg, x86_store_ssp_reg },
 # ifndef __x86_64__
 #  ifdef HAVE_PTRACE_GETFPXREGS
   { PTRACE_GETFPXREGS, PTRACE_SETFPXREGS, 0, sizeof (elf_fpxregset_t),
@@ -897,6 +912,17 @@ x86_linux_read_description ()
            {
              if (regset->nt_type == NT_X86_XSTATE)
                regset->size = xsave_len;
+             else if (regset->nt_type == NT_X86_SHSTK)
+               {
+                 /* We must configure the size of the NT_X86_SHSTK regset
+                    from non-zero value to it's appropriate size, even though
+                    the ptrace call is only tested for NT_X86_XSTATE request,
+                    because the NT_X86_SHSTK regset is of type
+                    OPTIONAL_RUNTIME_REGS.  A ptrace call with NT_X86_SHSTK
+                    request may only be successful later on, once shadow
+                    stack is enabled for the current thread.  */
+                   regset->size = sizeof (CORE_ADDR);
+               }
              else
                gdb_assert_not_reached ("invalid regset type.");
            }
index 9bb373c3100600fbf43f77920a98484874bc960a..6657c450c8890f1b6518a613d5349f315d783261 100644 (file)
@@ -28,6 +28,7 @@
 #define X86_XSTATE_ZMM_H_ID    6
 #define X86_XSTATE_ZMM_ID      7
 #define X86_XSTATE_PKRU_ID     9
+#define X86_XSTATE_CET_U_ID    11
 
 /* The extended state feature bits.  */
 #define X86_XSTATE_X87         (1ULL << X86_XSTATE_X87_ID)
@@ -42,6 +43,7 @@
                                 | X86_XSTATE_ZMM)
 
 #define X86_XSTATE_PKRU                (1ULL << X86_XSTATE_PKRU_ID)
+#define X86_XSTATE_CET_U       (1ULL << X86_XSTATE_CET_U_ID)
 
 /* Total size of the XSAVE area extended region and offsets of
    register states within the region.  Offsets are set to 0 to
@@ -86,7 +88,8 @@ constexpr bool operator!= (const x86_xsave_layout &lhs,
 /* Supported mask of state-component bitmap xstate_bv.  The SDM defines
    xstate_bv as XCR0 | IA32_XSS.  */
 
-#define X86_XSTATE_ALL_MASK            (X86_XSTATE_AVX_AVX512_PKU_MASK)
+#define X86_XSTATE_ALL_MASK            (X86_XSTATE_AVX_AVX512_PKU_MASK\
+                                       | X86_XSTATE_CET_U)
 
 #define X86_XSTATE_SSE_SIZE    576
 #define X86_XSTATE_AVX_SIZE    832