*** 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.
#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"
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
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 ();
-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. */
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;
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)
AMD64_ZMM0H_REGNUM,
AMD64_ZMM31H_REGNUM = AMD64_ZMM0H_REGNUM + 31,
AMD64_PKRU_REGNUM,
+ AMD64_PL3_SSP_REGNUM,
AMD64_FSBASE_REGNUM,
AMD64_GSBASE_REGNUM
};
#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. */
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 ();
}
#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. */
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 ();
}
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 },
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
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 \
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 \
--- /dev/null
+/* 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;
+}
--- /dev/null
+<?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>
--- /dev/null
+/* 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;
+}
--- /dev/null
+<?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>
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))
/* 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. */
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;
}
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)
/* 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;
I386_ZMM0H_REGNUM, /* %zmm0h */
I386_ZMM7H_REGNUM = I386_ZMM0H_REGNUM + 7,
I386_PKRU_REGNUM,
+ I386_PL3_SSP_REGNUM,
I386_FSBASE_REGNUM,
I386_GSBASE_REGNUM
};
= 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;
}
}
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"
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;
+}
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 */
--- /dev/null
+/* 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;
+}
--- /dev/null
+# 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
+ }
+}
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
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
+}
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.
#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. */
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)
}
\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. */
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 */
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). */
#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). */
-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]))
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)
{
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),
{
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.");
}
#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)
| 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
/* 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