* [PATCH v4 01/11] gdbserver: Add optional runtime register set type.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-19 9:27 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 02/11] gdbserver: Add assert in x86_linux_read_description Christina Schimpe
` (9 subsequent siblings)
10 siblings, 1 reply; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
Some register sets can be activated and deactivated by the OS during the runtime of
a process. One example register is the Intel CET shadow stack pointer. This patch
adds a new type of register set to handle such cases. We shouldn't deactivate these
regsets and should not show a warning if the register set is not active but supported
by the kernel. However, it is safe to deactivate them, if they are unsupported by the
kernel. To differentiate those scenarios we can use the errno returned by the ptrace
call.
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
gdbserver/linux-low.cc | 50 ++++++++++++++++++++++++++++++------------
gdbserver/linux-low.h | 7 +++++-
2 files changed, 42 insertions(+), 15 deletions(-)
diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 1d223c11db9..93fa14ddae9 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -5006,23 +5006,31 @@ regsets_fetch_inferior_registers (struct regsets_info *regsets_info,
if (res < 0)
{
if (errno == EIO
- || (errno == EINVAL && regset->type == OPTIONAL_REGS))
+ || (errno == EINVAL
+ && (regset->type == OPTIONAL_REGS
+ || regset->type == OPTIONAL_RUNTIME_REGS)))
{
/* If we get EIO on a regset, or an EINVAL and the regset is
- optional, do not try it again for this process mode. */
+ optional, do not try it again for this process mode.
+ Even if the regset can be enabled at runtime it is safe
+ to deactivate the regset in case of EINVAL, as we know
+ the regset itself was the invalid argument of the ptrace
+ call which means that it's unsupported by the kernel. */
disable_regset (regsets_info, regset);
}
- else if (errno == ENODATA)
+ else if (errno == ENODATA
+ || (errno == ENODEV
+ && regset->type == OPTIONAL_RUNTIME_REGS)
+ || errno == ESRCH)
{
- /* ENODATA may be returned if the regset is currently
- not "active". This can happen in normal operation,
- so suppress the warning in this case. */
- }
- else if (errno == ESRCH)
- {
- /* At this point, ESRCH should mean the process is
- already gone, in which case we simply ignore attempts
- to read its registers. */
+ /* ENODATA or ENODEV may be returned if the regset is
+ currently not "active". For ENODEV we additionally check
+ if the register set is of type OPTIONAL_RUNTIME_REGS.
+ This can happen in normal operation, so suppress the
+ warning in this case.
+ ESRCH should mean the process is already gone at this
+ point, in which case we simply ignore attempts to read
+ its registers. */
}
else
{
@@ -5104,12 +5112,26 @@ regsets_store_inferior_registers (struct regsets_info *regsets_info,
if (res < 0)
{
if (errno == EIO
- || (errno == EINVAL && regset->type == OPTIONAL_REGS))
+ || (errno == EINVAL
+ && (regset->type == OPTIONAL_REGS
+ || regset->type == OPTIONAL_RUNTIME_REGS)))
{
/* If we get EIO on a regset, or an EINVAL and the regset is
- optional, do not try it again for this process mode. */
+ optional, do not try it again for this process mode.
+ Even if the regset can be enabled at runtime it is safe
+ to deactivate the regset in case of EINVAL, as we know
+ the regset itself was the invalid argument of the ptrace
+ call which means that it's unsupported by the kernel. */
disable_regset (regsets_info, regset);
}
+ else if (errno == ENODEV
+ && regset->type == OPTIONAL_RUNTIME_REGS)
+ {
+ /* If we get ENODEV on a regset and the regset can be
+ enabled at runtime try it again for this process mode.
+ This can happen in normal operation, so suppress the
+ warning in this case. */
+ }
else if (errno == ESRCH)
{
/* At this point, ESRCH should mean the process is
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index 7d3e35fa9b0..8a2b4302c76 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -42,7 +42,12 @@ enum regset_type {
GENERAL_REGS,
FP_REGS,
EXTENDED_REGS,
- OPTIONAL_REGS, /* Do not error if the regset cannot be accessed. */
+ OPTIONAL_REGS, /* Do not error if the regset cannot be accessed.
+ Disable the regset instead. */
+ OPTIONAL_RUNTIME_REGS, /* Some optional regsets can only be accessed
+ dependent on the execution flow. For such
+ access errors don't show a warning and don't
+ disable the regset. */
};
/* The arch's regsets array initializer must be terminated with a NULL
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 01/11] gdbserver: Add optional runtime register set type.
2025-06-17 12:11 ` [PATCH v4 01/11] gdbserver: Add optional runtime register set type Christina Schimpe
@ 2025-06-19 9:27 ` Luis Machado
0 siblings, 0 replies; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:27 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> Some register sets can be activated and deactivated by the OS during the runtime of
> a process. One example register is the Intel CET shadow stack pointer. This patch
> adds a new type of register set to handle such cases. We shouldn't deactivate these
> regsets and should not show a warning if the register set is not active but supported
> by the kernel. However, it is safe to deactivate them, if they are unsupported by the
> kernel. To differentiate those scenarios we can use the errno returned by the ptrace
> call.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
> gdbserver/linux-low.cc | 50 ++++++++++++++++++++++++++++++------------
> gdbserver/linux-low.h | 7 +++++-
> 2 files changed, 42 insertions(+), 15 deletions(-)
>
> diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
> index 1d223c11db9..93fa14ddae9 100644
> --- a/gdbserver/linux-low.cc
> +++ b/gdbserver/linux-low.cc
> @@ -5006,23 +5006,31 @@ regsets_fetch_inferior_registers (struct regsets_info *regsets_info,
> if (res < 0)
> {
> if (errno == EIO
> - || (errno == EINVAL && regset->type == OPTIONAL_REGS))
> + || (errno == EINVAL
> + && (regset->type == OPTIONAL_REGS
> + || regset->type == OPTIONAL_RUNTIME_REGS)))
> {
> /* If we get EIO on a regset, or an EINVAL and the regset is
> - optional, do not try it again for this process mode. */
> + optional, do not try it again for this process mode.
> + Even if the regset can be enabled at runtime it is safe
> + to deactivate the regset in case of EINVAL, as we know
> + the regset itself was the invalid argument of the ptrace
> + call which means that it's unsupported by the kernel. */
> disable_regset (regsets_info, regset);
> }
> - else if (errno == ENODATA)
> + else if (errno == ENODATA
> + || (errno == ENODEV
> + && regset->type == OPTIONAL_RUNTIME_REGS)
> + || errno == ESRCH)
> {
> - /* ENODATA may be returned if the regset is currently
> - not "active". This can happen in normal operation,
> - so suppress the warning in this case. */
> - }
> - else if (errno == ESRCH)
> - {
> - /* At this point, ESRCH should mean the process is
> - already gone, in which case we simply ignore attempts
> - to read its registers. */
> + /* ENODATA or ENODEV may be returned if the regset is
> + currently not "active". For ENODEV we additionally check
> + if the register set is of type OPTIONAL_RUNTIME_REGS.
> + This can happen in normal operation, so suppress the
> + warning in this case.
> + ESRCH should mean the process is already gone at this
> + point, in which case we simply ignore attempts to read
> + its registers. */
> }
> else
> {
> @@ -5104,12 +5112,26 @@ regsets_store_inferior_registers (struct regsets_info *regsets_info,
> if (res < 0)
> {
> if (errno == EIO
> - || (errno == EINVAL && regset->type == OPTIONAL_REGS))
> + || (errno == EINVAL
> + && (regset->type == OPTIONAL_REGS
> + || regset->type == OPTIONAL_RUNTIME_REGS)))
> {
> /* If we get EIO on a regset, or an EINVAL and the regset is
> - optional, do not try it again for this process mode. */
> + optional, do not try it again for this process mode.
> + Even if the regset can be enabled at runtime it is safe
> + to deactivate the regset in case of EINVAL, as we know
> + the regset itself was the invalid argument of the ptrace
> + call which means that it's unsupported by the kernel. */
> disable_regset (regsets_info, regset);
> }
> + else if (errno == ENODEV
> + && regset->type == OPTIONAL_RUNTIME_REGS)
> + {
> + /* If we get ENODEV on a regset and the regset can be
> + enabled at runtime try it again for this process mode.
> + This can happen in normal operation, so suppress the
> + warning in this case. */
> + }
> else if (errno == ESRCH)
> {
> /* At this point, ESRCH should mean the process is
> diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
> index 7d3e35fa9b0..8a2b4302c76 100644
> --- a/gdbserver/linux-low.h
> +++ b/gdbserver/linux-low.h
> @@ -42,7 +42,12 @@ enum regset_type {
> GENERAL_REGS,
> FP_REGS,
> EXTENDED_REGS,
> - OPTIONAL_REGS, /* Do not error if the regset cannot be accessed. */
> + OPTIONAL_REGS, /* Do not error if the regset cannot be accessed.
> + Disable the regset instead. */
> + OPTIONAL_RUNTIME_REGS, /* Some optional regsets can only be accessed
> + dependent on the execution flow. For such
> + access errors don't show a warning and don't
> + disable the regset. */
> };
>
> /* The arch's regsets array initializer must be terminated with a NULL
This looks OK to me.
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 02/11] gdbserver: Add assert in x86_linux_read_description.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
2025-06-17 12:11 ` [PATCH v4 01/11] gdbserver: Add optional runtime register set type Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-19 9:27 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 03/11] gdb: Sync up x86-gcc-cpuid.h with cpuid.h from gcc 14 branch Christina Schimpe
` (8 subsequent siblings)
10 siblings, 1 reply; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
On x86 the PTRACE_GETREGSET request is currently only used for the xstate regset.
The size of the xstate regset is initialized to 0 such that it can be reset to
the appropriate size once we know it is supported for the current target
in x86_linux_read_description.
However, this configuration would not just affect the xstate regset but any regset
with PTRACE_GETREGSET request that is added in the future. The new regset would be
misconfigured with the xstate regset size. To avoid this we add an assert for
unsupported regsets and check explicitly for the note type of the register set.
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
gdbserver/linux-x86-low.cc | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/gdbserver/linux-x86-low.cc b/gdbserver/linux-x86-low.cc
index 918630d4b61..24920e71a53 100644
--- a/gdbserver/linux-x86-low.cc
+++ b/gdbserver/linux-x86-low.cc
@@ -894,7 +894,12 @@ x86_linux_read_description ()
regset++)
{
if (regset->get_request == PTRACE_GETREGSET)
- regset->size = xsave_len;
+ {
+ if (regset->nt_type == NT_X86_XSTATE)
+ regset->size = xsave_len;
+ else
+ gdb_assert_not_reached ("invalid regset type.");
+ }
else if (regset->type != GENERAL_REGS)
regset->size = 0;
}
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 02/11] gdbserver: Add assert in x86_linux_read_description.
2025-06-17 12:11 ` [PATCH v4 02/11] gdbserver: Add assert in x86_linux_read_description Christina Schimpe
@ 2025-06-19 9:27 ` Luis Machado
0 siblings, 0 replies; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:27 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> On x86 the PTRACE_GETREGSET request is currently only used for the xstate regset.
> The size of the xstate regset is initialized to 0 such that it can be reset to
> the appropriate size once we know it is supported for the current target
> in x86_linux_read_description.
>
> However, this configuration would not just affect the xstate regset but any regset
> with PTRACE_GETREGSET request that is added in the future. The new regset would be
> misconfigured with the xstate regset size. To avoid this we add an assert for
> unsupported regsets and check explicitly for the note type of the register set.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
> gdbserver/linux-x86-low.cc | 7 ++++++-
> 1 file changed, 6 insertions(+), 1 deletion(-)
>
> diff --git a/gdbserver/linux-x86-low.cc b/gdbserver/linux-x86-low.cc
> index 918630d4b61..24920e71a53 100644
> --- a/gdbserver/linux-x86-low.cc
> +++ b/gdbserver/linux-x86-low.cc
> @@ -894,7 +894,12 @@ x86_linux_read_description ()
> regset++)
> {
> if (regset->get_request == PTRACE_GETREGSET)
> - regset->size = xsave_len;
> + {
> + if (regset->nt_type == NT_X86_XSTATE)
> + regset->size = xsave_len;
> + else
> + gdb_assert_not_reached ("invalid regset type.");
> + }
> else if (regset->type != GENERAL_REGS)
> regset->size = 0;
> }
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 03/11] gdb: Sync up x86-gcc-cpuid.h with cpuid.h from gcc 14 branch.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
2025-06-17 12:11 ` [PATCH v4 01/11] gdbserver: Add optional runtime register set type Christina Schimpe
2025-06-17 12:11 ` [PATCH v4 02/11] gdbserver: Add assert in x86_linux_read_description Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-17 18:12 ` Tom Tromey
2025-06-17 12:11 ` [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86 Christina Schimpe
` (7 subsequent siblings)
10 siblings, 1 reply; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
This is required for a later commit which requires "bit_SHSTK".
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
gdb/nat/x86-gcc-cpuid.h | 153 ++++++++++++++++++++++++++++++++--------
1 file changed, 122 insertions(+), 31 deletions(-)
diff --git a/gdb/nat/x86-gcc-cpuid.h b/gdb/nat/x86-gcc-cpuid.h
index 14981005489..9e27fd88127 100644
--- a/gdb/nat/x86-gcc-cpuid.h
+++ b/gdb/nat/x86-gcc-cpuid.h
@@ -1,5 +1,5 @@
/*
- * Helper cpuid.h file copied from gcc-6.0.0. Code in gdb should not
+ * Helper cpuid.h file copied from gcc-14.2.0. Code in gdb should not
* include this directly, but pull in x86-cpuid.h and use that func.
*/
@@ -55,7 +55,7 @@
#define bit_SSE (1 << 25)
#define bit_SSE2 (1 << 26)
-/* Extended Features */
+/* Extended Features (%eax == 0x80000001) */
/* %ecx */
#define bit_LAHF_LM (1 << 0)
#define bit_ABM (1 << 5)
@@ -68,29 +68,28 @@
#define bit_MWAITX (1 << 29)
/* %edx */
-#define bit_AVX5124VNNIW (1 << 2)
-#define bit_AVX5124FMAPS (1 << 3)
#define bit_MMXEXT (1 << 22)
#define bit_LM (1 << 29)
#define bit_3DNOWP (1 << 30)
-#define bit_3DNOW (1 << 31)
+#define bit_3DNOW (1u << 31)
-/* %ebx. */
+/* %ebx */
#define bit_CLZERO (1 << 0)
+#define bit_WBNOINVD (1 << 9)
-/* Extended Features (%eax == 7) */
+/* Extended Features Leaf (%eax == 7, %ecx == 0) */
/* %ebx */
#define bit_FSGSBASE (1 << 0)
-#define bit_BMI (1 << 3)
-#define bit_HLE (1 << 4)
+#define bit_SGX (1 << 2)
+#define bit_BMI (1 << 3)
+#define bit_HLE (1 << 4)
#define bit_AVX2 (1 << 5)
#define bit_BMI2 (1 << 8)
-#define bit_RTM (1 << 11)
-#define bit_MPX (1 << 14)
+#define bit_RTM (1 << 11)
#define bit_AVX512F (1 << 16)
#define bit_AVX512DQ (1 << 17)
#define bit_RDSEED (1 << 18)
-#define bit_ADX (1 << 19)
+#define bit_ADX (1 << 19)
#define bit_AVX512IFMA (1 << 21)
#define bit_CLFLUSHOPT (1 << 23)
#define bit_CLWB (1 << 24)
@@ -99,23 +98,85 @@
#define bit_AVX512CD (1 << 28)
#define bit_SHA (1 << 29)
#define bit_AVX512BW (1 << 30)
-#define bit_AVX512VL (1 << 31)
+#define bit_AVX512VL (1u << 31)
/* %ecx */
-#define bit_PREFETCHWT1 (1 << 0)
+#define bit_PREFETCHWT1 (1 << 0)
#define bit_AVX512VBMI (1 << 1)
-#define bit_PKU (1 << 3)
+#define bit_PKU (1 << 3)
#define bit_OSPKE (1 << 4)
+#define bit_WAITPKG (1 << 5)
+#define bit_AVX512VBMI2 (1 << 6)
+#define bit_SHSTK (1 << 7)
+#define bit_GFNI (1 << 8)
+#define bit_VAES (1 << 9)
+#define bit_VPCLMULQDQ (1 << 10)
+#define bit_AVX512VNNI (1 << 11)
+#define bit_AVX512BITALG (1 << 12)
+#define bit_AVX512VPOPCNTDQ (1 << 14)
+#define bit_RDPID (1 << 22)
+#define bit_KL (1 << 23)
+#define bit_CLDEMOTE (1 << 25)
+#define bit_MOVDIRI (1 << 27)
+#define bit_MOVDIR64B (1 << 28)
+#define bit_ENQCMD (1 << 29)
-/* XFEATURE_ENABLED_MASK register bits (%eax == 13, %ecx == 0) */
-#define bit_BNDREGS (1 << 3)
-#define bit_BNDCSR (1 << 4)
+/* %edx */
+#define bit_AVX5124VNNIW (1 << 2)
+#define bit_AVX5124FMAPS (1 << 3)
+#define bit_UINTR (1 << 5)
+#define bit_AVX512VP2INTERSECT (1 << 8)
+#define bit_SERIALIZE (1 << 14)
+#define bit_TSXLDTRK (1 << 16)
+#define bit_PCONFIG (1 << 18)
+#define bit_IBT (1 << 20)
+#define bit_AMX_BF16 (1 << 22)
+#define bit_AVX512FP16 (1 << 23)
+#define bit_AMX_TILE (1 << 24)
+#define bit_AMX_INT8 (1 << 25)
+
+/* Extended Features Sub-leaf (%eax == 7, %ecx == 1) */
+/* %eax */
+#define bit_SHA512 (1 << 0)
+#define bit_SM3 (1 << 1)
+#define bit_SM4 (1 << 2)
+#define bit_RAOINT (1 << 3)
+#define bit_AVXVNNI (1 << 4)
+#define bit_AVX512BF16 (1 << 5)
+#define bit_CMPCCXADD (1 << 7)
+#define bit_AMX_COMPLEX (1 << 8)
+#define bit_AMX_FP16 (1 << 21)
+#define bit_HRESET (1 << 22)
+#define bit_AVXIFMA (1 << 23)
-/* Extended State Enumeration Sub-leaf (%eax == 13, %ecx == 1) */
+/* %edx */
+#define bit_AVXVNNIINT8 (1 << 4)
+#define bit_AVXNECONVERT (1 << 5)
+#define bit_AVXVNNIINT16 (1 << 10)
+#define bit_PREFETCHI (1 << 14)
+#define bit_USER_MSR (1 << 15)
+#define bit_AVX10 (1 << 19)
+#define bit_APX_F (1 << 21)
+
+/* Extended State Enumeration Sub-leaf (%eax == 0xd, %ecx == 1) */
#define bit_XSAVEOPT (1 << 0)
#define bit_XSAVEC (1 << 1)
#define bit_XSAVES (1 << 3)
+/* PT sub leaf (%eax == 0x14, %ecx == 0) */
+/* %ebx */
+#define bit_PTWRITE (1 << 4)
+
+/* Keylocker leaf (%eax == 0x19) */
+/* %ebx */
+#define bit_AESKLE ( 1<<0 )
+#define bit_WIDEKL ( 1<<2 )
+
+/* AVX10 sub leaf (%eax == 0x24) */
+/* %ebx */
+#define bit_AVX10_256 (1 << 17)
+#define bit_AVX10_512 (1 << 18)
+
/* Signatures for different CPU implementations as returned in uses
of cpuid with level 0. */
#define signature_AMD_ebx 0x68747541
@@ -170,19 +231,40 @@
#define signature_VORTEX_ecx 0x436f5320
#define signature_VORTEX_edx 0x36387865
-#define __cpuid(level, a, b, c, d) \
- __asm__ ("cpuid\n\t" \
- : "=a" (a), "=b" (b), "=c" (c), "=d" (d) \
- : "0" (level))
+#define signature_SHANGHAI_ebx 0x68532020
+#define signature_SHANGHAI_ecx 0x20206961
+#define signature_SHANGHAI_edx 0x68676e61
-#define __cpuid_count(level, count, a, b, c, d) \
- __asm__ ("cpuid\n\t" \
- : "=a" (a), "=b" (b), "=c" (c), "=d" (d) \
- : "0" (level), "2" (count))
+#ifndef __x86_64__
+/* At least one cpu (Winchip 2) does not set %ebx and %ecx
+ for cpuid leaf 1. Forcibly zero the two registers before
+ calling cpuid as a precaution. */
+#define __cpuid(level, a, b, c, d) \
+ do { \
+ if (__builtin_constant_p (level) && (level) != 1) \
+ __asm__ __volatile__ ("cpuid\n\t" \
+ : "=a" (a), "=b" (b), "=c" (c), "=d" (d) \
+ : "0" (level)); \
+ else \
+ __asm__ __volatile__ ("cpuid\n\t" \
+ : "=a" (a), "=b" (b), "=c" (c), "=d" (d) \
+ : "0" (level), "1" (0), "2" (0)); \
+ } while (0)
+#else
+#define __cpuid(level, a, b, c, d) \
+ __asm__ __volatile__ ("cpuid\n\t" \
+ : "=a" (a), "=b" (b), "=c" (c), "=d" (d) \
+ : "0" (level))
+#endif
+
+#define __cpuid_count(level, count, a, b, c, d) \
+ __asm__ __volatile__ ("cpuid\n\t" \
+ : "=a" (a), "=b" (b), "=c" (c), "=d" (d) \
+ : "0" (level), "2" (count))
/* Return highest supported input value for cpuid instruction. ext can
- be either 0x0 or 0x8000000 to return highest supported value for
+ be either 0x0 or 0x80000000 to return highest supported value for
basic or extended cpuid information. Function returns 0 if cpuid
is not supported or whatever cpuid returns in eax register. If sig
pointer is non-null, then first four bytes of the signature
@@ -225,7 +307,7 @@ __get_cpuid_max (unsigned int __ext, unsigned int *__sig)
: "i" (0x00200000));
#endif
- if (!((__eax ^ __ebx) & 0x00200000))
+ if (__builtin_expect (!((__eax ^ __ebx) & 0x00200000), 0))
return 0;
#endif
@@ -249,8 +331,9 @@ __get_cpuid (unsigned int __leaf,
unsigned int *__ecx, unsigned int *__edx)
{
unsigned int __ext = __leaf & 0x80000000;
+ unsigned int __maxlevel = __get_cpuid_max (__ext, 0);
- if (__get_cpuid_max (__ext, 0) < __leaf)
+ if (__maxlevel == 0 || __maxlevel < __leaf)
return 0;
__cpuid (__leaf, *__eax, *__ebx, *__ecx, *__edx);
@@ -265,12 +348,20 @@ __get_cpuid_count (unsigned int __leaf, unsigned int __subleaf,
unsigned int *__ecx, unsigned int *__edx)
{
unsigned int __ext = __leaf & 0x80000000;
+ unsigned int __maxlevel = __get_cpuid_max (__ext, 0);
- if (__get_cpuid_max (__ext, 0) < __leaf)
+ if (__builtin_expect (__maxlevel == 0, 0) || __maxlevel < __leaf)
return 0;
__cpuid_count (__leaf, __subleaf, *__eax, *__ebx, *__ecx, *__edx);
return 1;
}
+static __inline void
+__cpuidex (int __cpuid_info[4], int __leaf, int __subleaf)
+{
+ __cpuid_count (__leaf, __subleaf, __cpuid_info[0], __cpuid_info[1],
+ __cpuid_info[2], __cpuid_info[3]);
+}
+
#endif /* GDB_NAT_X86_GCC_CPUID_H */
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 03/11] gdb: Sync up x86-gcc-cpuid.h with cpuid.h from gcc 14 branch.
2025-06-17 12:11 ` [PATCH v4 03/11] gdb: Sync up x86-gcc-cpuid.h with cpuid.h from gcc 14 branch Christina Schimpe
@ 2025-06-17 18:12 ` Tom Tromey
2025-06-20 12:39 ` Schimpe, Christina
0 siblings, 1 reply; 53+ messages in thread
From: Tom Tromey @ 2025-06-17 18:12 UTC (permalink / raw)
To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann, eliz
>>>>> "Christina" == Christina Schimpe <christina.schimpe@intel.com> writes:
Christina> This is required for a later commit which requires "bit_SHSTK".
If this file is canonically maintained in gcc then this seems totally
fine to me.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 53+ messages in thread
* RE: [PATCH v4 03/11] gdb: Sync up x86-gcc-cpuid.h with cpuid.h from gcc 14 branch.
2025-06-17 18:12 ` Tom Tromey
@ 2025-06-20 12:39 ` Schimpe, Christina
0 siblings, 0 replies; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-20 12:39 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches, thiago.bauermann
Hi Tom,
Thank you for the review!
> If this file is canonically maintained in gcc then this seems totally fine to me.
That's what I'd understand from the comment at the beginning of this file:
/*
* Helper cpuid.h file copied from gcc-14.2.0. Code in gdb should not
* include this directly, but pull in x86-cpuid.h and use that func.
*/
> Approved-By: Tom Tromey <tom@tromey.com>
I'll push the patch together with this series, so I'd wait until it is fully approved.
As currently only this series requires that update.
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
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
` (2 preceding siblings ...)
2025-06-17 12:11 ` [PATCH v4 03/11] gdb: Sync up x86-gcc-cpuid.h with cpuid.h from gcc 14 branch Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-19 9:23 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register Christina Schimpe
` (6 subsequent siblings)
10 siblings, 1 reply; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
The XSAVE features set is organized in state components, which are a set
of or parts of registers. So-called XSAVE-supported features are
organized using state-component bitmaps, each bit corresponding to a
single state component.
The SDM uses the term xstate_bv for a state-component bitmap, which is
defined as XCR0 | IA32_XSS. The control register XCR0 only contains a
state-component bitmap that specifies user state components, while IA32_XSS
contains a state-component bitmap that specifies supervisor state components.
Until now, XCR0 is used as input for target description creation in GDB.
However, a following patch will add userspace support for the CET shadow
stack feature by Intel. The CET state is configured in IA32_XSS and consists
of 2 state components:
- State component 11 used for the 2 MSRs controlling user-mode
functionality for CET (CET_U state)
- State component 12 used for the 3 MSRs containing shadow-stack pointers
for privilege levels 0-2 (CET_S state).
Reading the CET shadow stack pointer register on linux requires a separate
ptrace call using NT_X86_SHSTK. To pass the CET shadow stack enablement
state we would like to pass the xstate_bv value instead of xcr0 for target
description creation. To prepare for that, we rename the xcr0 mask
values for target description creation to xstate_bv. However, this
patch doesn't add any functional changes in GDB.
Future states specified in IA32_XSS such as CET will create a combined
xstate_bv_mask including xcr0 register value and its corresponding bit in
the state component bitmap. This combined mask will then be used to create
the target descriptions.
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
gdb/amd64-tdep.c | 14 +++----
gdb/amd64-tdep.h | 8 +++-
gdb/arch/amd64-linux-tdesc.c | 33 ++++++++--------
gdb/arch/amd64-linux-tdesc.h | 7 ++--
gdb/arch/amd64.c | 15 +++-----
gdb/arch/amd64.h | 10 ++++-
gdb/arch/i386-linux-tdesc.c | 29 +++++++-------
gdb/arch/i386-linux-tdesc.h | 5 ++-
gdb/arch/i386.c | 15 ++++----
gdb/arch/i386.h | 8 +++-
gdb/arch/x86-linux-tdesc-features.c | 59 +++++++++++++++--------------
gdb/arch/x86-linux-tdesc-features.h | 25 +++++++-----
gdb/i386-tdep.c | 14 +++----
gdb/i386-tdep.h | 7 +++-
gdb/nat/x86-linux-tdesc.c | 18 +++++----
gdb/nat/x86-linux-tdesc.h | 7 ++--
gdb/x86-linux-nat.c | 11 ++++--
gdbserver/i387-fp.cc | 40 +++++++++----------
gdbserver/linux-amd64-ipa.cc | 10 +++--
gdbserver/linux-i386-ipa.cc | 6 +--
gdbserver/linux-x86-low.cc | 9 ++---
gdbsupport/x86-xstate.h | 4 +-
22 files changed, 198 insertions(+), 156 deletions(-)
diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c
index e4957783502..e4a0ecf7ac9 100644
--- a/gdb/amd64-tdep.c
+++ b/gdb/amd64-tdep.c
@@ -3523,23 +3523,23 @@ amd64_x32_none_init_abi (gdbarch_info info, gdbarch *arch)
amd64_target_description (X86_XSTATE_SSE_MASK, true));
}
-/* Return the target description for a specified XSAVE feature mask. */
+/* See amd64-tdep.h. */
const struct target_desc *
-amd64_target_description (uint64_t xcr0, bool segments)
+amd64_target_description (uint64_t xstate_bv_mask, bool segments)
{
static target_desc *amd64_tdescs \
[2/*AVX*/][2/*AVX512*/][2/*PKRU*/][2/*segments*/] = {};
target_desc **tdesc;
- tdesc = &amd64_tdescs[(xcr0 & X86_XSTATE_AVX) ? 1 : 0]
- [(xcr0 & X86_XSTATE_AVX512) ? 1 : 0]
- [(xcr0 & X86_XSTATE_PKRU) ? 1 : 0]
+ tdesc = &amd64_tdescs[(xstate_bv_mask & X86_XSTATE_AVX) ? 1 : 0]
+ [(xstate_bv_mask & X86_XSTATE_AVX512) ? 1 : 0]
+ [(xstate_bv_mask & X86_XSTATE_PKRU) ? 1 : 0]
[segments ? 1 : 0];
if (*tdesc == NULL)
- *tdesc = amd64_create_target_description (xcr0, false, false,
- segments);
+ *tdesc = amd64_create_target_description (xstate_bv_mask, false,
+ false, segments);
return *tdesc;
}
diff --git a/gdb/amd64-tdep.h b/gdb/amd64-tdep.h
index cbf8a97fa3c..82f781bf404 100644
--- a/gdb/amd64-tdep.h
+++ b/gdb/amd64-tdep.h
@@ -108,8 +108,12 @@ extern void amd64_init_abi (struct gdbarch_info info,
extern void amd64_x32_init_abi (struct gdbarch_info info,
struct gdbarch *gdbarch,
const target_desc *default_tdesc);
-extern const struct target_desc *amd64_target_description (uint64_t xcr0,
- bool segments);
+
+/* Return the target description for the specified xsave features as
+ defined in XSTATE_BV_MASK and SEGMENTS. */
+
+extern const struct target_desc *amd64_target_description
+ (uint64_t xstate_bv_mask, bool segments);
/* Fill register REGNUM in REGCACHE with the appropriate
floating-point or SSE register value from *FXSAVE. If REGNUM is
diff --git a/gdb/arch/amd64-linux-tdesc.c b/gdb/arch/amd64-linux-tdesc.c
index 91de75873a1..771badc0ca7 100644
--- a/gdb/arch/amd64-linux-tdesc.c
+++ b/gdb/arch/amd64-linux-tdesc.c
@@ -26,41 +26,42 @@
/* See arch/amd64-linux-tdesc.h. */
const struct target_desc *
-amd64_linux_read_description (uint64_t xcr0, bool is_x32)
+amd64_linux_read_description (uint64_t xstate_bv_mask, bool is_x32)
{
/* The type used for the amd64 and x32 target description caches. */
using tdesc_cache_type = std::unordered_map<uint64_t, const target_desc_up>;
/* Caches for the previously seen amd64 and x32 target descriptions,
- indexed by the xcr0 value that created the target description. These
- need to be static within this function to ensure they are initialised
- before first use. */
+ indexed by the xstate_bv_mask value that created the target
+ description. These need to be static within this function to ensure
+ they are initialised before first use. */
static tdesc_cache_type amd64_tdesc_cache, x32_tdesc_cache;
tdesc_cache_type &tdesc_cache = is_x32 ? x32_tdesc_cache : amd64_tdesc_cache;
- /* Only some bits are checked when creating a tdesc, but the XCR0 value
- contains other feature bits that are not relevant for tdesc creation.
- When indexing into the TDESC_CACHE we need to use a consistent xcr0
- value otherwise we might fail to find an existing tdesc which has the
- same set of relevant bits set. */
- xcr0 &= is_x32
- ? x86_linux_x32_xcr0_feature_mask ()
- : x86_linux_amd64_xcr0_feature_mask ();
+ /* Only some bits are checked when creating a tdesc, but the
+ xstate_bv_mask value contains other feature bits that are not
+ relevant for tdesc creation.
+ When indexing into the TDESC_CACHE we need to use a consistent
+ xstate_bv_mask value otherwise we might fail to find an existing
+ tdesc which has the same set of relevant bits set. */
+ xstate_bv_mask &= is_x32
+ ? x86_linux_x32_xstate_bv_feature_mask ()
+ : x86_linux_amd64_xstate_bv_feature_mask ();
- const auto it = tdesc_cache.find (xcr0);
+ const auto it = tdesc_cache.find (xstate_bv_mask);
if (it != tdesc_cache.end ())
return it->second.get ();
/* Create the previously unseen target description. */
- target_desc_up tdesc (amd64_create_target_description (xcr0, is_x32,
- true, true));
+ target_desc_up tdesc (amd64_create_target_description (xstate_bv_mask,
+ is_x32, true, true));
x86_linux_post_init_tdesc (tdesc.get (), true);
/* Add to the cache, and return a pointer borrowed from the
target_desc_up. This is safe as the cache (and the pointers contained
within it) are not deleted until GDB exits. */
target_desc *ptr = tdesc.get ();
- tdesc_cache.emplace (xcr0, std::move (tdesc));
+ tdesc_cache.emplace (xstate_bv_mask, std::move (tdesc));
return ptr;
}
diff --git a/gdb/arch/amd64-linux-tdesc.h b/gdb/arch/amd64-linux-tdesc.h
index 8806a132562..0d0e1bbff72 100644
--- a/gdb/arch/amd64-linux-tdesc.h
+++ b/gdb/arch/amd64-linux-tdesc.h
@@ -22,9 +22,10 @@
struct target_desc;
-/* Return the AMD64 target descriptions corresponding to XCR0 and IS_X32. */
+/* Return the AMD64 target descriptions corresponding to XSTATE_BV_MASK
+ and IS_X32. */
-extern const target_desc *amd64_linux_read_description (uint64_t xcr0,
- bool is_x32);
+extern const target_desc *amd64_linux_read_description
+ (uint64_t xstate_bv_mask, bool is_x32);
#endif /* GDB_ARCH_AMD64_LINUX_TDESC_H */
diff --git a/gdb/arch/amd64.c b/gdb/arch/amd64.c
index 252650b6390..9cac0645cdc 100644
--- a/gdb/arch/amd64.c
+++ b/gdb/arch/amd64.c
@@ -30,14 +30,11 @@
#include "../features/i386/x32-core.c"
-/* Create amd64 target descriptions according to XCR0. If IS_X32 is
- true, create the x32 ones. If IS_LINUX is true, create target
- descriptions for Linux. If SEGMENTS is true, then include
- the "org.gnu.gdb.i386.segments" feature registers. */
+/* See amd64.h. */
target_desc *
-amd64_create_target_description (uint64_t xcr0, bool is_x32, bool is_linux,
- bool segments)
+amd64_create_target_description (uint64_t xstate_bv_mask, bool is_x32,
+ bool is_linux, bool segments)
{
target_desc_up tdesc = allocate_target_description ();
@@ -62,13 +59,13 @@ amd64_create_target_description (uint64_t xcr0, bool is_x32, bool is_linux,
if (segments)
regnum = create_feature_i386_64bit_segments (tdesc.get (), regnum);
- if (xcr0 & X86_XSTATE_AVX)
+ if (xstate_bv_mask & X86_XSTATE_AVX)
regnum = create_feature_i386_64bit_avx (tdesc.get (), regnum);
- if (xcr0 & X86_XSTATE_AVX512)
+ if (xstate_bv_mask & X86_XSTATE_AVX512)
regnum = create_feature_i386_64bit_avx512 (tdesc.get (), regnum);
- if (xcr0 & X86_XSTATE_PKRU)
+ if (xstate_bv_mask & X86_XSTATE_PKRU)
regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
return tdesc.release ();
diff --git a/gdb/arch/amd64.h b/gdb/arch/amd64.h
index 695660c55d6..54080ee10a9 100644
--- a/gdb/arch/amd64.h
+++ b/gdb/arch/amd64.h
@@ -21,7 +21,13 @@
#include "gdbsupport/tdesc.h"
#include <stdint.h>
-target_desc *amd64_create_target_description (uint64_t xcr0, bool is_x32,
- bool is_linux, bool segments);
+/* Create amd64 target descriptions according to XSTATE_BV_MASK. If
+ IS_X32 is true, create the x32 ones. If IS_LINUX is true, create
+ target descriptions for Linux. If SEGMENTS is true, then include
+ the "org.gnu.gdb.i386.segments" feature registers. */
+
+target_desc *amd64_create_target_description (uint64_t xstate_bv_mask,
+ bool is_x32, bool is_linux,
+ bool segments);
#endif /* GDB_ARCH_AMD64_H */
diff --git a/gdb/arch/i386-linux-tdesc.c b/gdb/arch/i386-linux-tdesc.c
index 51513176464..57cdbbfa7e5 100644
--- a/gdb/arch/i386-linux-tdesc.c
+++ b/gdb/arch/i386-linux-tdesc.c
@@ -25,32 +25,35 @@
/* See arch/i386-linux-tdesc.h. */
const target_desc *
-i386_linux_read_description (uint64_t xcr0)
+i386_linux_read_description (uint64_t xstate_bv_mask)
{
- /* Cache of previously seen i386 target descriptions, indexed by the xcr0
- value that created the target description. This needs to be static
- within this function to ensure it is initialised before first use. */
+ /* Cache of previously seen i386 target descriptions, indexed by the
+ xstate_bv_mask value that created the target description. This
+ needs to be static within this function to ensure it is initialised
+ before first use. */
static std::unordered_map<uint64_t, const target_desc_up> i386_tdesc_cache;
- /* Only some bits are checked when creating a tdesc, but the XCR0 value
- contains other feature bits that are not relevant for tdesc creation.
- When indexing into the I386_TDESC_CACHE we need to use a consistent
- xcr0 value otherwise we might fail to find an existing tdesc which has
- the same set of relevant bits set. */
- xcr0 &= x86_linux_i386_xcr0_feature_mask ();
+ /* Only some bits are checked when creating a tdesc, but the
+ XSTATE_BV_MASK value contains other feature bits that are not
+ relevant for tdesc creation. When indexing into the I386_TDESC_CACHE
+ we need to use a consistent XSTATE_BV_MASK value otherwise we might
+ fail to find an existing tdesc which has the same set of relevant
+ bits set. */
+ xstate_bv_mask &= x86_linux_i386_xstate_bv_feature_mask ();
- const auto it = i386_tdesc_cache.find (xcr0);
+ const auto it = i386_tdesc_cache.find (xstate_bv_mask);
if (it != i386_tdesc_cache.end ())
return it->second.get ();
/* Create the previously unseen target description. */
- target_desc_up tdesc (i386_create_target_description (xcr0, true, false));
+ target_desc_up tdesc
+ (i386_create_target_description (xstate_bv_mask, true, false));
x86_linux_post_init_tdesc (tdesc.get (), false);
/* Add to the cache, and return a pointer borrowed from the
target_desc_up. This is safe as the cache (and the pointers contained
within it) are not deleted until GDB exits. */
target_desc *ptr = tdesc.get ();
- i386_tdesc_cache.emplace (xcr0, std::move (tdesc));
+ i386_tdesc_cache.emplace (xstate_bv_mask, std::move (tdesc));
return ptr;
}
diff --git a/gdb/arch/i386-linux-tdesc.h b/gdb/arch/i386-linux-tdesc.h
index 2c3c1740d81..3392b3fa06e 100644
--- a/gdb/arch/i386-linux-tdesc.h
+++ b/gdb/arch/i386-linux-tdesc.h
@@ -22,8 +22,9 @@
struct target_desc;
-/* Return the i386 target description corresponding to XCR0. */
+/* Return the i386 target description corresponding to XSTATE_BV_MASK. */
-extern const struct target_desc *i386_linux_read_description (uint64_t xcr0);
+extern const struct target_desc *i386_linux_read_description
+ (uint64_t xstate_bv_mask);
#endif /* GDB_ARCH_I386_LINUX_TDESC_H */
diff --git a/gdb/arch/i386.c b/gdb/arch/i386.c
index 835df53c75d..84f31439e7a 100644
--- a/gdb/arch/i386.c
+++ b/gdb/arch/i386.c
@@ -29,10 +29,11 @@
#include "../features/i386/32bit-segments.c"
#include "../features/i386/pkeys.c"
-/* Create i386 target descriptions according to XCR0. */
+/* See i386.h. */
target_desc *
-i386_create_target_description (uint64_t xcr0, bool is_linux, bool segments)
+i386_create_target_description (uint64_t xstate_bv_mask, bool is_linux,
+ bool segments)
{
target_desc_up tdesc = allocate_target_description ();
@@ -44,10 +45,10 @@ i386_create_target_description (uint64_t xcr0, bool is_linux, bool segments)
long regnum = 0;
- if (xcr0 & X86_XSTATE_X87)
+ if (xstate_bv_mask & X86_XSTATE_X87)
regnum = create_feature_i386_32bit_core (tdesc.get (), regnum);
- if (xcr0 & X86_XSTATE_SSE)
+ if (xstate_bv_mask & X86_XSTATE_SSE)
regnum = create_feature_i386_32bit_sse (tdesc.get (), regnum);
if (is_linux)
@@ -56,13 +57,13 @@ i386_create_target_description (uint64_t xcr0, bool is_linux, bool segments)
if (segments)
regnum = create_feature_i386_32bit_segments (tdesc.get (), regnum);
- if (xcr0 & X86_XSTATE_AVX)
+ if (xstate_bv_mask & X86_XSTATE_AVX)
regnum = create_feature_i386_32bit_avx (tdesc.get (), regnum);
- if (xcr0 & X86_XSTATE_AVX512)
+ if (xstate_bv_mask & X86_XSTATE_AVX512)
regnum = create_feature_i386_32bit_avx512 (tdesc.get (), regnum);
- if (xcr0 & X86_XSTATE_PKRU)
+ if (xstate_bv_mask & X86_XSTATE_PKRU)
regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
return tdesc.release ();
diff --git a/gdb/arch/i386.h b/gdb/arch/i386.h
index 1fc41014624..a23deebc366 100644
--- a/gdb/arch/i386.h
+++ b/gdb/arch/i386.h
@@ -21,7 +21,13 @@
#include "gdbsupport/tdesc.h"
#include <stdint.h>
-target_desc *i386_create_target_description (uint64_t xcr0, bool is_linux,
+/* Create i386 target descriptions according to XSTATE_BV_MASK. If
+ IS_LINUX is true, create target descriptions for Linux. If SEGMENTS
+ is true, then include the "org.gnu.gdb.i386.segments" feature
+ registers. */
+
+target_desc *i386_create_target_description (uint64_t xstate_bv_mask,
+ bool is_linux,
bool segments);
#endif /* GDB_ARCH_I386_H */
diff --git a/gdb/arch/x86-linux-tdesc-features.c b/gdb/arch/x86-linux-tdesc-features.c
index f65920cb95b..68f37fccaef 100644
--- a/gdb/arch/x86-linux-tdesc-features.c
+++ b/gdb/arch/x86-linux-tdesc-features.c
@@ -28,18 +28,21 @@
We want to cache target descriptions, and this is currently done in
three separate caches, one each for i386, amd64, and x32. Additionally,
- the caching we're discussing here is Linux only, and for Linux, the only
- thing that has an impact on target description creation is the xcr0
- value.
-
- In order to ensure the cache functions correctly we need to filter out
- only those xcr0 feature bits that are relevant, we can then cache target
- descriptions based on the relevant feature bits. Two xcr0 values might
- be different, but have the same relevant feature bits. In this case we
- would expect the two xcr0 values to map to the same cache entry. */
+ the caching we're discussing here is Linux only. Currently for Linux,
+ the only thing that has an impact on target description creation are
+ the supported features in xsave which are modelled by a xstate_bv_mask
+ value, which has the same format than the state component bitmap.
+
+ In order to ensure the cache functions correctly we need to filter only
+ those xstate_bv_mask feature bits that are relevant, we can then cache
+ target descriptions based on the relevant feature bits. Two
+ xstate_bv_mask values might be different, but have the same relevant
+ feature bits. In this case we would expect the two xstate_bv_mask
+ values to map to the same cache entry. */
struct x86_xstate_feature {
- /* The xstate feature mask. This is a mask against an xcr0 value. */
+ /* The xstate feature mask. This is a mask against the state component
+ bitmap. */
uint64_t feature;
/* Is this feature checked when creating an i386 target description. */
@@ -56,9 +59,9 @@ struct x86_xstate_feature {
checked when building a target description for i386, amd64, or x32.
If in the future, due to simplifications or refactoring, this table ever
- ends up with 'true' for every xcr0 feature on every target type, then this
- is an indication that this table should probably be removed, and that the
- rest of the code in this file can be simplified. */
+ ends up with 'true' for every xsave feature on every target type, then
+ this is an indication that this table should probably be removed, and
+ that the rest of the code in this file can be simplified. */
static constexpr x86_xstate_feature x86_linux_all_xstate_features[] = {
/* Feature, i386, amd64, x32. */
@@ -73,7 +76,7 @@ static constexpr x86_xstate_feature x86_linux_all_xstate_features[] = {
that are checked for when building an i386 target description. */
static constexpr uint64_t
-x86_linux_i386_xcr0_feature_mask_1 ()
+x86_linux_i386_xstate_bv_feature_mask_1 ()
{
uint64_t mask = 0;
@@ -88,7 +91,7 @@ x86_linux_i386_xcr0_feature_mask_1 ()
that are checked for when building an amd64 target description. */
static constexpr uint64_t
-x86_linux_amd64_xcr0_feature_mask_1 ()
+x86_linux_amd64_xstate_bv_feature_mask_1 ()
{
uint64_t mask = 0;
@@ -103,7 +106,7 @@ x86_linux_amd64_xcr0_feature_mask_1 ()
that are checked for when building an x32 target description. */
static constexpr uint64_t
-x86_linux_x32_xcr0_feature_mask_1 ()
+x86_linux_x32_xstate_bv_feature_mask_1 ()
{
uint64_t mask = 0;
@@ -117,25 +120,25 @@ x86_linux_x32_xcr0_feature_mask_1 ()
/* See arch/x86-linux-tdesc-features.h. */
uint64_t
-x86_linux_i386_xcr0_feature_mask ()
+x86_linux_i386_xstate_bv_feature_mask ()
{
- return x86_linux_i386_xcr0_feature_mask_1 ();
+ return x86_linux_i386_xstate_bv_feature_mask_1 ();
}
/* See arch/x86-linux-tdesc-features.h. */
uint64_t
-x86_linux_amd64_xcr0_feature_mask ()
+x86_linux_amd64_xstate_bv_feature_mask ()
{
- return x86_linux_amd64_xcr0_feature_mask_1 ();
+ return x86_linux_amd64_xstate_bv_feature_mask_1 ();
}
/* See arch/x86-linux-tdesc-features.h. */
uint64_t
-x86_linux_x32_xcr0_feature_mask ()
+x86_linux_x32_xstate_bv_feature_mask ()
{
- return x86_linux_x32_xcr0_feature_mask_1 ();
+ return x86_linux_x32_xstate_bv_feature_mask_1 ();
}
#ifdef GDBSERVER
@@ -143,7 +146,7 @@ x86_linux_x32_xcr0_feature_mask ()
/* See arch/x86-linux-tdesc-features.h. */
int
-x86_linux_xcr0_to_tdesc_idx (uint64_t xcr0)
+x86_linux_xstate_bv_mask_to_tdesc_idx (uint64_t xstate_bv_mask)
{
/* The following table shows which features are checked for when creating
the target descriptions (see nat/x86-linux-tdesc.c), the feature order
@@ -160,7 +163,7 @@ x86_linux_xcr0_to_tdesc_idx (uint64_t xcr0)
for (int i = 0; i < ARRAY_SIZE (x86_linux_all_xstate_features); ++i)
{
- if ((xcr0 & x86_linux_all_xstate_features[i].feature)
+ if ((xstate_bv_mask & x86_linux_all_xstate_features[i].feature)
== x86_linux_all_xstate_features[i].feature)
idx |= (1 << i);
}
@@ -250,17 +253,17 @@ x86_linux_i386_tdesc_count ()
/* See arch/x86-linux-tdesc-features.h. */
uint64_t
-x86_linux_tdesc_idx_to_xcr0 (int idx)
+x86_linux_tdesc_idx_to_xstate_bv_mask (int idx)
{
- uint64_t xcr0 = 0;
+ uint64_t xstate_bv_mask = 0;
for (int i = 0; i < ARRAY_SIZE (x86_linux_all_xstate_features); ++i)
{
if ((idx & (1 << i)) != 0)
- xcr0 |= x86_linux_all_xstate_features[i].feature;
+ xstate_bv_mask |= x86_linux_all_xstate_features[i].feature;
}
- return xcr0;
+ return xstate_bv_mask;
}
#endif /* IN_PROCESS_AGENT */
diff --git a/gdb/arch/x86-linux-tdesc-features.h b/gdb/arch/x86-linux-tdesc-features.h
index 89fe7cecc68..d1d74e70df8 100644
--- a/gdb/arch/x86-linux-tdesc-features.h
+++ b/gdb/arch/x86-linux-tdesc-features.h
@@ -27,17 +27,20 @@
the set of features which are checked for when creating the target
description for each of amd64, x32, and i386. */
-extern uint64_t x86_linux_amd64_xcr0_feature_mask ();
-extern uint64_t x86_linux_x32_xcr0_feature_mask ();
-extern uint64_t x86_linux_i386_xcr0_feature_mask ();
+extern uint64_t x86_linux_amd64_xstate_bv_feature_mask ();
+extern uint64_t x86_linux_x32_xstate_bv_feature_mask ();
+extern uint64_t x86_linux_i386_xstate_bv_feature_mask ();
#ifdef GDBSERVER
-/* Convert an xcr0 value into an integer. The integer will be passed from
+/* Convert an XSTATE_BV_MASK value into an integer. XSTATE_BV_MASK has
+ the same format than the state component bitmap and does include user
+ and supervisor state components. The integer will be passed from
gdbserver to the in-process-agent where it will then be passed through
- x86_linux_tdesc_idx_to_xcr0 to get back the original xcr0 value. */
+ x86_linux_tdesc_idx_to_xstate_bv_mask to get back the original mask. */
-extern int x86_linux_xcr0_to_tdesc_idx (uint64_t xcr0);
+
+extern int x86_linux_xstate_bv_mask_to_tdesc_idx (uint64_t xstate_bv_mask);
#endif /* GDBSERVER */
@@ -51,11 +54,13 @@ extern int x86_linux_amd64_tdesc_count ();
extern int x86_linux_x32_tdesc_count ();
extern int x86_linux_i386_tdesc_count ();
-/* Convert an index number (as returned from x86_linux_xcr0_to_tdesc_idx)
- into an xcr0 value which can then be used to create a target
- description. */
+/* Convert an index number (as returned from
+ x86_linux_xstate_bv_mask_to_tdesc_idx) into an xstate_bv_mask
+ value which can then be used to create a target description.
+ The return mask has the same format than the state component bitmap
+ and does include user and supervisor state components.*/
-extern uint64_t x86_linux_tdesc_idx_to_xcr0 (int idx);
+extern uint64_t x86_linux_tdesc_idx_to_xstate_bv_mask (int idx);
#endif /* IN_PROCESS_AGENT */
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index 23788b44e92..eca462f0f1b 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -8949,23 +8949,23 @@ i386_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
\f
-/* Return the target description for a specified XSAVE feature mask. */
+/* See i386-tdep.h. */
const struct target_desc *
-i386_target_description (uint64_t xcr0, bool segments)
+i386_target_description (uint64_t xstate_bv_mask, bool segments)
{
static target_desc *i386_tdescs \
[2/*SSE*/][2/*AVX*/][2/*AVX512*/][2/*PKRU*/][2/*segments*/] = {};
target_desc **tdesc;
- tdesc = &i386_tdescs[(xcr0 & X86_XSTATE_SSE) ? 1 : 0]
- [(xcr0 & X86_XSTATE_AVX) ? 1 : 0]
- [(xcr0 & X86_XSTATE_AVX512) ? 1 : 0]
- [(xcr0 & X86_XSTATE_PKRU) ? 1 : 0]
+ tdesc = &i386_tdescs[(xstate_bv_mask & X86_XSTATE_SSE) ? 1 : 0]
+ [(xstate_bv_mask & X86_XSTATE_AVX) ? 1 : 0]
+ [(xstate_bv_mask & X86_XSTATE_AVX512) ? 1 : 0]
+ [(xstate_bv_mask & X86_XSTATE_PKRU) ? 1 : 0]
[segments ? 1 : 0];
if (*tdesc == NULL)
- *tdesc = i386_create_target_description (xcr0, false, segments);
+ *tdesc = i386_create_target_description (xstate_bv_mask, false, segments);
return *tdesc;
}
diff --git a/gdb/i386-tdep.h b/gdb/i386-tdep.h
index e849b336e12..239bc8674e8 100644
--- a/gdb/i386-tdep.h
+++ b/gdb/i386-tdep.h
@@ -450,8 +450,11 @@ extern int i386_svr4_reg_to_regnum (struct gdbarch *gdbarch, int reg);
extern int i386_process_record (struct gdbarch *gdbarch,
struct regcache *regcache, CORE_ADDR addr);
-extern const struct target_desc *i386_target_description (uint64_t xcr0,
- bool segments);
+
+/* Return the target description for the specified xsave features as
+ defined in XSTATE_BV_MASK and SEGMENTS. */
+extern const struct target_desc *i386_target_description
+ (uint64_t xstate_bv_mask, bool segments);
/* Functions and variables exported from i386-bsd-tdep.c. */
diff --git a/gdb/nat/x86-linux-tdesc.c b/gdb/nat/x86-linux-tdesc.c
index 80e4337a270..e9cf2527c5f 100644
--- a/gdb/nat/x86-linux-tdesc.c
+++ b/gdb/nat/x86-linux-tdesc.c
@@ -43,7 +43,7 @@
/* See nat/x86-linux-tdesc.h. */
const target_desc *
-x86_linux_tdesc_for_tid (int tid, uint64_t *xcr0_storage,
+x86_linux_tdesc_for_tid (int tid, uint64_t *xstate_bv_storage,
x86_xsave_layout *xsave_layout_storage)
{
#ifdef __x86_64__
@@ -96,30 +96,32 @@ x86_linux_tdesc_for_tid (int tid, uint64_t *xcr0_storage,
these bits being set we generate a completely empty tdesc for
i386 which will be rejected by GDB. */
have_ptrace_getregset = TRIBOOL_FALSE;
- *xcr0_storage = X86_XSTATE_SSE_MASK;
+ *xstate_bv_storage = X86_XSTATE_SSE_MASK;
}
else
{
have_ptrace_getregset = TRIBOOL_TRUE;
/* Get XCR0 from XSAVE extended state. */
- *xcr0_storage = xstateregs[(I386_LINUX_XSAVE_XCR0_OFFSET
+ uint64_t xcr0 = xstateregs[(I386_LINUX_XSAVE_XCR0_OFFSET
/ sizeof (uint64_t))];
*xsave_layout_storage
- = x86_fetch_xsave_layout (*xcr0_storage, x86_xsave_length ());
+ = x86_fetch_xsave_layout (xcr0, x86_xsave_length ());
+
+ *xstate_bv_storage = xcr0;
}
}
- /* Use cached xcr0 value. */
- uint64_t xcr0_features_bits = *xcr0_storage & X86_XSTATE_ALL_MASK;
+ /* Use cached XSTATE_BV_STORAGE value. */
+ uint64_t xstate_bv_features_bits = *xstate_bv_storage & X86_XSTATE_ALL_MASK;
#ifdef __x86_64__
if (is_64bit)
- return amd64_linux_read_description (xcr0_features_bits, is_x32);
+ return amd64_linux_read_description (xstate_bv_features_bits, is_x32);
else
#endif
- return i386_linux_read_description (xcr0_features_bits);
+ return i386_linux_read_description (xstate_bv_features_bits);
}
#endif /* !IN_PROCESS_AGENT */
diff --git a/gdb/nat/x86-linux-tdesc.h b/gdb/nat/x86-linux-tdesc.h
index 38c71f10df2..19aa84f6b3f 100644
--- a/gdb/nat/x86-linux-tdesc.h
+++ b/gdb/nat/x86-linux-tdesc.h
@@ -27,9 +27,9 @@ struct x86_xsave_layout;
/* Return the target description for Linux thread TID.
- The storage pointed to by XCR0_STORAGE and XSAVE_LAYOUT_STORAGE must
+ The storage pointed to by XSTATE_BV_STORAGE and XSAVE_LAYOUT_STORAGE must
exist until the program (GDB or gdbserver) terminates, this storage is
- used to cache the xcr0 and xsave layout values. The values pointed to
+ used to cache the xstate_bv and xsave layout values. The values pointed to
by these arguments are only updated at most once, the first time this
function is called if the have_ptrace_getregset global is set to
TRIBOOL_UNKNOWN.
@@ -45,6 +45,7 @@ struct x86_xsave_layout;
returned. */
extern const target_desc *x86_linux_tdesc_for_tid
- (int tid, uint64_t *xcr0_storage, x86_xsave_layout *xsave_layout_storage);
+ (int tid, uint64_t *xstate_bv_storage,
+ x86_xsave_layout *xsave_layout_storage);
#endif /* GDB_NAT_X86_LINUX_TDESC_H */
diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c
index fc7c5f642ed..a82ad21da27 100644
--- a/gdb/x86-linux-nat.c
+++ b/gdb/x86-linux-nat.c
@@ -97,15 +97,20 @@ 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 xcr0 value is stored here and reused on subsequent calls. */
- static uint64_t xcr0_storage;
+ 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 XSTATE_BV_STORAGE will not be configured based on
+ xcr0 only. */
+ static uint64_t xstate_bv_storage;
if (inferior_ptid == null_ptid)
return this->beneath ()->read_description ();
int tid = inferior_ptid.pid ();
- return x86_linux_tdesc_for_tid (tid, &xcr0_storage, &this->m_xsave_layout);
+ return x86_linux_tdesc_for_tid (tid, &xstate_bv_storage,
+ &this->m_xsave_layout);
}
\f
diff --git a/gdbserver/i387-fp.cc b/gdbserver/i387-fp.cc
index 4be0083a6fb..90824bdaee5 100644
--- a/gdbserver/i387-fp.cc
+++ b/gdbserver/i387-fp.cc
@@ -21,7 +21,7 @@
#include "nat/x86-xstate.h"
/* Default to SSE. */
-static uint64_t x86_xcr0 = X86_XSTATE_SSE_MASK;
+static uint64_t x86_xstate_bv = X86_XSTATE_SSE_MASK;
static const int num_avx512_k_registers = 8;
static const int num_pkeys_registers = 1;
@@ -265,7 +265,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
/* The supported bits in `xstat_bv' are 8 bytes. Clear part in
vector registers if its bit in xstat_bv is zero. */
- clear_bv = (~fp->xstate_bv) & x86_xcr0;
+ clear_bv = (~fp->xstate_bv) & x86_xstate_bv;
/* Clear part in x87 and vector registers if its bit in xstat_bv is
zero. */
@@ -315,7 +315,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
/* Check if any x87 registers are changed. */
- if ((x86_xcr0 & X86_XSTATE_X87))
+ if ((x86_xstate_bv & X86_XSTATE_X87))
{
int st0_regnum = find_regno (regcache->tdesc, "st0");
@@ -332,7 +332,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
/* Check if any SSE registers are changed. */
- if ((x86_xcr0 & X86_XSTATE_SSE))
+ if ((x86_xstate_bv & X86_XSTATE_SSE))
{
int xmm0_regnum = find_regno (regcache->tdesc, "xmm0");
@@ -349,7 +349,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
/* Check if any AVX registers are changed. */
- if ((x86_xcr0 & X86_XSTATE_AVX))
+ if ((x86_xstate_bv & X86_XSTATE_AVX))
{
int ymm0h_regnum = find_regno (regcache->tdesc, "ymm0h");
@@ -366,7 +366,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
/* Check if any K registers are changed. */
- if ((x86_xcr0 & X86_XSTATE_K))
+ if ((x86_xstate_bv & X86_XSTATE_K))
{
int k0_regnum = find_regno (regcache->tdesc, "k0");
@@ -383,7 +383,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
/* Check if any of ZMM0H-ZMM15H registers are changed. */
- if ((x86_xcr0 & X86_XSTATE_ZMM_H))
+ if ((x86_xstate_bv & X86_XSTATE_ZMM_H))
{
int zmm0h_regnum = find_regno (regcache->tdesc, "zmm0h");
@@ -400,7 +400,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
/* Check if any of ZMM16-ZMM31 registers are changed. */
- if ((x86_xcr0 & X86_XSTATE_ZMM) && num_zmm_high_registers != 0)
+ if ((x86_xstate_bv & X86_XSTATE_ZMM) && num_zmm_high_registers != 0)
{
int zmm16h_regnum = find_regno (regcache->tdesc, "zmm16h");
int ymm16h_regnum = find_regno (regcache->tdesc, "ymm16h");
@@ -437,7 +437,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
/* Check if any PKEYS registers are changed. */
- if ((x86_xcr0 & X86_XSTATE_PKRU))
+ if ((x86_xstate_bv & X86_XSTATE_PKRU))
{
int pkru_regnum = find_regno (regcache->tdesc, "pkru");
@@ -453,7 +453,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
}
- if ((x86_xcr0 & X86_XSTATE_SSE) || (x86_xcr0 & X86_XSTATE_AVX))
+ if ((x86_xstate_bv & X86_XSTATE_SSE) || (x86_xstate_bv & X86_XSTATE_AVX))
{
collect_register_by_name (regcache, "mxcsr", raw);
if (memcmp (raw, &fp->mxcsr, 4) != 0)
@@ -465,7 +465,7 @@ i387_cache_to_xsave (struct regcache *regcache, void *buf)
}
}
- if (x86_xcr0 & X86_XSTATE_X87)
+ if (x86_xstate_bv & X86_XSTATE_X87)
{
collect_register_by_name (regcache, "fioff", raw);
if (memcmp (raw, &fp->fioff, 4) != 0)
@@ -658,10 +658,10 @@ i387_xsave_to_cache (struct regcache *regcache, const void *buf)
/* The supported bits in `xstat_bv' are 8 bytes. Clear part in
vector registers if its bit in xstat_bv is zero. */
- clear_bv = (~fp->xstate_bv) & x86_xcr0;
+ clear_bv = (~fp->xstate_bv) & x86_xstate_bv;
/* Check if any x87 registers are changed. */
- if ((x86_xcr0 & X86_XSTATE_X87) != 0)
+ if ((x86_xstate_bv & X86_XSTATE_X87) != 0)
{
int st0_regnum = find_regno (regcache->tdesc, "st0");
@@ -678,7 +678,7 @@ i387_xsave_to_cache (struct regcache *regcache, const void *buf)
}
}
- if ((x86_xcr0 & X86_XSTATE_SSE) != 0)
+ if ((x86_xstate_bv & X86_XSTATE_SSE) != 0)
{
int xmm0_regnum = find_regno (regcache->tdesc, "xmm0");
@@ -695,7 +695,7 @@ i387_xsave_to_cache (struct regcache *regcache, const void *buf)
}
}
- if ((x86_xcr0 & X86_XSTATE_AVX) != 0)
+ if ((x86_xstate_bv & X86_XSTATE_AVX) != 0)
{
int ymm0h_regnum = find_regno (regcache->tdesc, "ymm0h");
@@ -712,7 +712,7 @@ i387_xsave_to_cache (struct regcache *regcache, const void *buf)
}
}
- if ((x86_xcr0 & X86_XSTATE_K) != 0)
+ if ((x86_xstate_bv & X86_XSTATE_K) != 0)
{
int k0_regnum = find_regno (regcache->tdesc, "k0");
@@ -729,7 +729,7 @@ i387_xsave_to_cache (struct regcache *regcache, const void *buf)
}
}
- if ((x86_xcr0 & X86_XSTATE_ZMM_H) != 0)
+ if ((x86_xstate_bv & X86_XSTATE_ZMM_H) != 0)
{
int zmm0h_regnum = find_regno (regcache->tdesc, "zmm0h");
@@ -746,7 +746,7 @@ i387_xsave_to_cache (struct regcache *regcache, const void *buf)
}
}
- if ((x86_xcr0 & X86_XSTATE_ZMM) != 0 && num_zmm_high_registers != 0)
+ if ((x86_xstate_bv & X86_XSTATE_ZMM) != 0 && num_zmm_high_registers != 0)
{
int zmm16h_regnum = find_regno (regcache->tdesc, "zmm16h");
int ymm16h_regnum = find_regno (regcache->tdesc, "ymm16h");
@@ -773,7 +773,7 @@ i387_xsave_to_cache (struct regcache *regcache, const void *buf)
}
}
- if ((x86_xcr0 & X86_XSTATE_PKRU) != 0)
+ if ((x86_xstate_bv & X86_XSTATE_PKRU) != 0)
{
int pkru_regnum = find_regno (regcache->tdesc, "pkru");
@@ -858,5 +858,5 @@ i387_xsave_to_cache (struct regcache *regcache, const void *buf)
std::pair<uint64_t *, x86_xsave_layout *>
i387_get_xsave_storage ()
{
- return { &x86_xcr0, &xsave_layout };
+ return { &x86_xstate_bv, &xsave_layout };
}
diff --git a/gdbserver/linux-amd64-ipa.cc b/gdbserver/linux-amd64-ipa.cc
index af4eb03b9b8..75d70a8787c 100644
--- a/gdbserver/linux-amd64-ipa.cc
+++ b/gdbserver/linux-amd64-ipa.cc
@@ -82,7 +82,7 @@ get_raw_reg (const unsigned char *raw_regs, int regnum)
const struct target_desc *
get_ipa_tdesc (int idx)
{
- uint64_t xcr0 = x86_linux_tdesc_idx_to_xcr0 (idx);
+ uint64_t xstate_bv_mask = x86_linux_tdesc_idx_to_xstate_bv_mask (idx);
#if defined __ILP32__
bool is_x32 = true;
@@ -90,7 +90,7 @@ get_ipa_tdesc (int idx)
bool is_x32 = false;
#endif
- return amd64_linux_read_description (xcr0, is_x32);
+ return amd64_linux_read_description (xstate_bv_mask, is_x32);
}
/* Allocate buffer for the jump pads. The branch instruction has a
@@ -159,9 +159,11 @@ initialize_low_tracepoint (void)
{
#if defined __ILP32__
for (int i = 0; i < x86_linux_x32_tdesc_count (); i++)
- amd64_linux_read_description (x86_linux_tdesc_idx_to_xcr0 (i), true);
+ amd64_linux_read_description
+ (x86_linux_tdesc_idx_to_xstate_bv_mask (i), true);
#else
for (int i = 0; i < x86_linux_amd64_tdesc_count (); i++)
- amd64_linux_read_description (x86_linux_tdesc_idx_to_xcr0 (i), false);
+ amd64_linux_read_description
+ (x86_linux_tdesc_idx_to_xstate_bv_mask (i), false);
#endif
}
diff --git a/gdbserver/linux-i386-ipa.cc b/gdbserver/linux-i386-ipa.cc
index 17af6eb3610..1a0393d127c 100644
--- a/gdbserver/linux-i386-ipa.cc
+++ b/gdbserver/linux-i386-ipa.cc
@@ -174,9 +174,9 @@ initialize_fast_tracepoint_trampoline_buffer (void)
const struct target_desc *
get_ipa_tdesc (int idx)
{
- uint64_t xcr0 = x86_linux_tdesc_idx_to_xcr0 (idx);
+ uint64_t xstate_bv_mask = x86_linux_tdesc_idx_to_xstate_bv_mask (idx);
- return i386_linux_read_description (xcr0);
+ return i386_linux_read_description (xstate_bv_mask);
}
/* Allocate buffer for the jump pads. On i386, we can reach an arbitrary
@@ -199,5 +199,5 @@ initialize_low_tracepoint (void)
{
initialize_fast_tracepoint_trampoline_buffer ();
for (int i = 0; i < x86_linux_i386_tdesc_count (); i++)
- i386_linux_read_description (x86_linux_tdesc_idx_to_xcr0 (i));
+ i386_linux_read_description (x86_linux_tdesc_idx_to_xstate_bv_mask (i));
}
diff --git a/gdbserver/linux-x86-low.cc b/gdbserver/linux-x86-low.cc
index 24920e71a53..dbc11d6856d 100644
--- a/gdbserver/linux-x86-low.cc
+++ b/gdbserver/linux-x86-low.cc
@@ -873,7 +873,7 @@ x86_linux_read_description ()
bool have_ptrace_getregset_was_unknown
= have_ptrace_getregset == TRIBOOL_UNKNOWN;
- /* Get pointers to where we should store the xcr0 and xsave_layout
+ /* Get pointers to where we should store the xstate_bv and xsave_layout
values. These will be filled in by x86_linux_tdesc_for_tid the first
time that the function is called. Subsequent calls will not modify
the stored values. */
@@ -2892,17 +2892,16 @@ x86_target::get_ipa_tdesc_idx ()
|| tdesc == tdesc_amd64_linux_no_xml.get ()
#endif /* __x86_64__ */
);
- return x86_linux_xcr0_to_tdesc_idx (X86_XSTATE_SSE_MASK);
+ return x86_linux_xstate_bv_mask_to_tdesc_idx (X86_XSTATE_SSE_MASK);
}
- /* The xcr0 value and xsave layout value are cached when the target
+ /* The xstate_bv value and xsave layout value are cached when the target
description is read. Grab their cache location, and use the cached
value to calculate a tdesc index. */
std::pair<uint64_t *, x86_xsave_layout *> storage
= i387_get_xsave_storage ();
- uint64_t xcr0 = *storage.first;
- return x86_linux_xcr0_to_tdesc_idx (xcr0);
+ return x86_linux_xstate_bv_mask_to_tdesc_idx (*storage.first);
}
/* The linux target ops object. */
diff --git a/gdbsupport/x86-xstate.h b/gdbsupport/x86-xstate.h
index 5d563ff4622..9bb373c3100 100644
--- a/gdbsupport/x86-xstate.h
+++ b/gdbsupport/x86-xstate.h
@@ -83,8 +83,10 @@ constexpr bool operator!= (const x86_xsave_layout &lhs,
#define X86_XSTATE_AVX_AVX512_PKU_MASK (X86_XSTATE_AVX_MASK\
| X86_XSTATE_AVX512 | X86_XSTATE_PKRU)
-#define X86_XSTATE_ALL_MASK (X86_XSTATE_AVX_AVX512_PKU_MASK)
+/* 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_SSE_SIZE 576
#define X86_XSTATE_AVX_SIZE 832
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86.
2025-06-17 12:11 ` [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86 Christina Schimpe
@ 2025-06-19 9:23 ` Luis Machado
2025-06-23 12:46 ` Schimpe, Christina
0 siblings, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:23 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> diff --git a/gdb/arch/amd64.c b/gdb/arch/amd64.c
> index 252650b6390..9cac0645cdc 100644
> --- a/gdb/arch/amd64.c
> +++ b/gdb/arch/amd64.c
> @@ -30,14 +30,11 @@
>
> #include "../features/i386/x32-core.c"
>
> -/* Create amd64 target descriptions according to XCR0. If IS_X32 is
> - true, create the x32 ones. If IS_LINUX is true, create target
> - descriptions for Linux. If SEGMENTS is true, then include
> - the "org.gnu.gdb.i386.segments" feature registers. */
> +/* See amd64.h. */
More appropriate to reference arch/amd64.h.
>
> target_desc *
> -amd64_create_target_description (uint64_t xcr0, bool is_x32, bool is_linux,
> - bool segments)
> +amd64_create_target_description (uint64_t xstate_bv_mask, bool is_x32,
> + bool is_linux, bool segments)
> {
> target_desc_up tdesc = allocate_target_description ();
>
> @@ -62,13 +59,13 @@ amd64_create_target_description (uint64_t xcr0, bool is_x32, bool is_linux,
> if (segments)
> regnum = create_feature_i386_64bit_segments (tdesc.get (), regnum);
>
> - if (xcr0 & X86_XSTATE_AVX)
> + if (xstate_bv_mask & X86_XSTATE_AVX)
> regnum = create_feature_i386_64bit_avx (tdesc.get (), regnum);
>
> - if (xcr0 & X86_XSTATE_AVX512)
> + if (xstate_bv_mask & X86_XSTATE_AVX512)
> regnum = create_feature_i386_64bit_avx512 (tdesc.get (), regnum);
>
> - if (xcr0 & X86_XSTATE_PKRU)
> + if (xstate_bv_mask & X86_XSTATE_PKRU)
> regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
>
> return tdesc.release ();
<...>
> diff --git a/gdb/arch/i386.c b/gdb/arch/i386.c
> index 835df53c75d..84f31439e7a 100644
> --- a/gdb/arch/i386.c
> +++ b/gdb/arch/i386.c
> @@ -29,10 +29,11 @@
> #include "../features/i386/32bit-segments.c"
> #include "../features/i386/pkeys.c"
>
> -/* Create i386 target descriptions according to XCR0. */
> +/* See i386.h. */
arch/i386.h
>
> target_desc *
> -i386_create_target_description (uint64_t xcr0, bool is_linux, bool segments)
> +i386_create_target_description (uint64_t xstate_bv_mask, bool is_linux,
> + bool segments)
> {
> target_desc_up tdesc = allocate_target_description ();
>
> @@ -44,10 +45,10 @@ i386_create_target_description (uint64_t xcr0, bool is_linux, bool segments)
>
> long regnum = 0;
>
> - if (xcr0 & X86_XSTATE_X87)
> + if (xstate_bv_mask & X86_XSTATE_X87)
> regnum = create_feature_i386_32bit_core (tdesc.get (), regnum);
>
> - if (xcr0 & X86_XSTATE_SSE)
> + if (xstate_bv_mask & X86_XSTATE_SSE)
> regnum = create_feature_i386_32bit_sse (tdesc.get (), regnum);
>
> if (is_linux)
> @@ -56,13 +57,13 @@ i386_create_target_description (uint64_t xcr0, bool is_linux, bool segments)
> if (segments)
> regnum = create_feature_i386_32bit_segments (tdesc.get (), regnum);
>
> - if (xcr0 & X86_XSTATE_AVX)
> + if (xstate_bv_mask & X86_XSTATE_AVX)
> regnum = create_feature_i386_32bit_avx (tdesc.get (), regnum);
>
> - if (xcr0 & X86_XSTATE_AVX512)
> + if (xstate_bv_mask & X86_XSTATE_AVX512)
> regnum = create_feature_i386_32bit_avx512 (tdesc.get (), regnum);
>
> - if (xcr0 & X86_XSTATE_PKRU)
> + if (xstate_bv_mask & X86_XSTATE_PKRU)
> regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
>
> return tdesc.release ();
<...>
> diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c
> index fc7c5f642ed..a82ad21da27 100644
> --- a/gdb/x86-linux-nat.c
> +++ b/gdb/x86-linux-nat.c
> @@ -97,15 +97,20 @@ 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 xcr0 value is stored here and reused on subsequent calls. */
> - static uint64_t xcr0_storage;
> + 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 XSTATE_BV_STORAGE will not be configured based on
> + xcr0 only. */
Something about the comment above reads off in the last phrase. Is there a missing
connection word?
> + static uint64_t xstate_bv_storage;
>
> if (inferior_ptid == null_ptid)
> return this->beneath ()->read_description ();
>
> int tid = inferior_ptid.pid ();
>
> - return x86_linux_tdesc_for_tid (tid, &xcr0_storage, &this->m_xsave_layout);
> + return x86_linux_tdesc_for_tid (tid, &xstate_bv_storage,
> + &this->m_xsave_layout);
> }
> \f
>
Otherwise this looks OK to me. I suppose this was regression tested? I'd be a bit more
comfortable if someone more knowledgeable could chime in before this gets approved.
But if nobody shows up, we can get it in if others have no concerns.
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86.
2025-06-19 9:23 ` Luis Machado
@ 2025-06-23 12:46 ` Schimpe, Christina
2025-06-23 12:56 ` Luis Machado
0 siblings, 1 reply; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-23 12:46 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
Hi Luis,
Thanks a lot for the feedback. Please find my comments below.
> -----Original Message-----
> From: Luis Machado <luis.machado@arm.com>
> Sent: Thursday, June 19, 2025 11:24 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> Subject: Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target
> description creation on x86.
>
> On 6/17/25 13:11, Christina Schimpe wrote:
> > diff --git a/gdb/arch/amd64.c b/gdb/arch/amd64.c index
> > 252650b6390..9cac0645cdc 100644
> > --- a/gdb/arch/amd64.c
> > +++ b/gdb/arch/amd64.c
> > @@ -30,14 +30,11 @@
> >
> > #include "../features/i386/x32-core.c"
> >
> > -/* Create amd64 target descriptions according to XCR0. If IS_X32 is
> > - true, create the x32 ones. If IS_LINUX is true, create target
> > - descriptions for Linux. If SEGMENTS is true, then include
> > - the "org.gnu.gdb.i386.segments" feature registers. */
> > +/* See amd64.h. */
>
> More appropriate to reference arch/amd64.h.
True, will fix.
>
> >
> > target_desc *
> > -amd64_create_target_description (uint64_t xcr0, bool is_x32, bool is_linux,
> > - bool segments)
> > +amd64_create_target_description (uint64_t xstate_bv_mask, bool is_x32,
> > + bool is_linux, bool segments)
> > {
> > target_desc_up tdesc = allocate_target_description ();
> >
> > @@ -62,13 +59,13 @@ amd64_create_target_description (uint64_t xcr0,
> bool is_x32, bool is_linux,
> > if (segments)
> > regnum = create_feature_i386_64bit_segments (tdesc.get (),
> > regnum);
> >
> > - if (xcr0 & X86_XSTATE_AVX)
> > + if (xstate_bv_mask & X86_XSTATE_AVX)
> > regnum = create_feature_i386_64bit_avx (tdesc.get (), regnum);
> >
> > - if (xcr0 & X86_XSTATE_AVX512)
> > + if (xstate_bv_mask & X86_XSTATE_AVX512)
> > regnum = create_feature_i386_64bit_avx512 (tdesc.get (), regnum);
> >
> > - if (xcr0 & X86_XSTATE_PKRU)
> > + if (xstate_bv_mask & X86_XSTATE_PKRU)
> > regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
> >
> > return tdesc.release ();
>
> <...>
>
> > diff --git a/gdb/arch/i386.c b/gdb/arch/i386.c index
> > 835df53c75d..84f31439e7a 100644
> > --- a/gdb/arch/i386.c
> > +++ b/gdb/arch/i386.c
> > @@ -29,10 +29,11 @@
> > #include "../features/i386/32bit-segments.c"
> > #include "../features/i386/pkeys.c"
> >
> > -/* Create i386 target descriptions according to XCR0. */
> > +/* See i386.h. */
>
> arch/i386.h
Will fix.
>
> >
> > target_desc *
> > -i386_create_target_description (uint64_t xcr0, bool is_linux, bool
> > segments)
> > +i386_create_target_description (uint64_t xstate_bv_mask, bool is_linux,
> > + bool segments)
> > {
> > target_desc_up tdesc = allocate_target_description ();
> >
> > @@ -44,10 +45,10 @@ i386_create_target_description (uint64_t xcr0,
> > bool is_linux, bool segments)
> >
> > long regnum = 0;
> >
> > - if (xcr0 & X86_XSTATE_X87)
> > + if (xstate_bv_mask & X86_XSTATE_X87)
> > regnum = create_feature_i386_32bit_core (tdesc.get (), regnum);
> >
> > - if (xcr0 & X86_XSTATE_SSE)
> > + if (xstate_bv_mask & X86_XSTATE_SSE)
> > regnum = create_feature_i386_32bit_sse (tdesc.get (), regnum);
> >
> > if (is_linux)
> > @@ -56,13 +57,13 @@ i386_create_target_description (uint64_t xcr0, bool
> is_linux, bool segments)
> > if (segments)
> > regnum = create_feature_i386_32bit_segments (tdesc.get (),
> > regnum);
> >
> > - if (xcr0 & X86_XSTATE_AVX)
> > + if (xstate_bv_mask & X86_XSTATE_AVX)
> > regnum = create_feature_i386_32bit_avx (tdesc.get (), regnum);
> >
> > - if (xcr0 & X86_XSTATE_AVX512)
> > + if (xstate_bv_mask & X86_XSTATE_AVX512)
> > regnum = create_feature_i386_32bit_avx512 (tdesc.get (), regnum);
> >
> > - if (xcr0 & X86_XSTATE_PKRU)
> > + if (xstate_bv_mask & X86_XSTATE_PKRU)
> > regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
> >
> > return tdesc.release ();
>
> <...>
>
> > diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c index
> > fc7c5f642ed..a82ad21da27 100644
> > --- a/gdb/x86-linux-nat.c
> > +++ b/gdb/x86-linux-nat.c
> > @@ -97,15 +97,20 @@ 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 xcr0 value is stored here and reused on subsequent calls. */
> > - static uint64_t xcr0_storage;
> > + 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 XSTATE_BV_STORAGE will not be configured based on
> > + xcr0 only. */
>
> Something about the comment above reads off in the last phrase. Is there a
> missing connection word?
I agree, would this be a bit better?
"However, once supervisor state components are supported in GDB, the value
XSTATE_BV_STORAGE will not be configured based on xcr0 only."
>
> > + static uint64_t xstate_bv_storage;
> >
> > if (inferior_ptid == null_ptid)
> > return this->beneath ()->read_description ();
> >
> > int tid = inferior_ptid.pid ();
> >
> > - return x86_linux_tdesc_for_tid (tid, &xcr0_storage,
> > &this->m_xsave_layout);
> > + return x86_linux_tdesc_for_tid (tid, &xstate_bv_storage,
> > + &this->m_xsave_layout);
> > }
> >
> Otherwise this looks OK to me. I suppose this was regression tested? I'd be a bit
> more comfortable if someone more knowledgeable could chime in before this
> gets approved.
>
> But if nobody shows up, we can get it in if others have no concerns.
>
> Reviewed-By: Luis Machado <luis.machado@arm.com>
Yes, this has been regression tested (recent testing for v4 on ubuntu 24.04).
I am currently working on the setup for testing x32 again. This configuration has been tested for v1 initially only.
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86.
2025-06-23 12:46 ` Schimpe, Christina
@ 2025-06-23 12:56 ` Luis Machado
2025-06-24 13:46 ` Schimpe, Christina
0 siblings, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-23 12:56 UTC (permalink / raw)
To: Schimpe, Christina, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/23/25 13:46, Schimpe, Christina wrote:
> Hi Luis,
>
> Thanks a lot for the feedback. Please find my comments below.
>
>> -----Original Message-----
>> From: Luis Machado <luis.machado@arm.com>
>> Sent: Thursday, June 19, 2025 11:24 AM
>> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>> patches@sourceware.org
>> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
>> Subject: Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target
>> description creation on x86.
>>
>> On 6/17/25 13:11, Christina Schimpe wrote:
>>> diff --git a/gdb/arch/amd64.c b/gdb/arch/amd64.c index
>>> 252650b6390..9cac0645cdc 100644
>>> --- a/gdb/arch/amd64.c
>>> +++ b/gdb/arch/amd64.c
>>> @@ -30,14 +30,11 @@
>>>
>>> #include "../features/i386/x32-core.c"
>>>
>>> -/* Create amd64 target descriptions according to XCR0. If IS_X32 is
>>> - true, create the x32 ones. If IS_LINUX is true, create target
>>> - descriptions for Linux. If SEGMENTS is true, then include
>>> - the "org.gnu.gdb.i386.segments" feature registers. */
>>> +/* See amd64.h. */
>>
>> More appropriate to reference arch/amd64.h.
>
> True, will fix.
>
>>
>>>
>>> target_desc *
>>> -amd64_create_target_description (uint64_t xcr0, bool is_x32, bool is_linux,
>>> - bool segments)
>>> +amd64_create_target_description (uint64_t xstate_bv_mask, bool is_x32,
>>> + bool is_linux, bool segments)
>>> {
>>> target_desc_up tdesc = allocate_target_description ();
>>>
>>> @@ -62,13 +59,13 @@ amd64_create_target_description (uint64_t xcr0,
>> bool is_x32, bool is_linux,
>>> if (segments)
>>> regnum = create_feature_i386_64bit_segments (tdesc.get (),
>>> regnum);
>>>
>>> - if (xcr0 & X86_XSTATE_AVX)
>>> + if (xstate_bv_mask & X86_XSTATE_AVX)
>>> regnum = create_feature_i386_64bit_avx (tdesc.get (), regnum);
>>>
>>> - if (xcr0 & X86_XSTATE_AVX512)
>>> + if (xstate_bv_mask & X86_XSTATE_AVX512)
>>> regnum = create_feature_i386_64bit_avx512 (tdesc.get (), regnum);
>>>
>>> - if (xcr0 & X86_XSTATE_PKRU)
>>> + if (xstate_bv_mask & X86_XSTATE_PKRU)
>>> regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
>>>
>>> return tdesc.release ();
>>
>> <...>
>>
>>> diff --git a/gdb/arch/i386.c b/gdb/arch/i386.c index
>>> 835df53c75d..84f31439e7a 100644
>>> --- a/gdb/arch/i386.c
>>> +++ b/gdb/arch/i386.c
>>> @@ -29,10 +29,11 @@
>>> #include "../features/i386/32bit-segments.c"
>>> #include "../features/i386/pkeys.c"
>>>
>>> -/* Create i386 target descriptions according to XCR0. */
>>> +/* See i386.h. */
>>
>> arch/i386.h
>
> Will fix.
>
>>
>>>
>>> target_desc *
>>> -i386_create_target_description (uint64_t xcr0, bool is_linux, bool
>>> segments)
>>> +i386_create_target_description (uint64_t xstate_bv_mask, bool is_linux,
>>> + bool segments)
>>> {
>>> target_desc_up tdesc = allocate_target_description ();
>>>
>>> @@ -44,10 +45,10 @@ i386_create_target_description (uint64_t xcr0,
>>> bool is_linux, bool segments)
>>>
>>> long regnum = 0;
>>>
>>> - if (xcr0 & X86_XSTATE_X87)
>>> + if (xstate_bv_mask & X86_XSTATE_X87)
>>> regnum = create_feature_i386_32bit_core (tdesc.get (), regnum);
>>>
>>> - if (xcr0 & X86_XSTATE_SSE)
>>> + if (xstate_bv_mask & X86_XSTATE_SSE)
>>> regnum = create_feature_i386_32bit_sse (tdesc.get (), regnum);
>>>
>>> if (is_linux)
>>> @@ -56,13 +57,13 @@ i386_create_target_description (uint64_t xcr0, bool
>> is_linux, bool segments)
>>> if (segments)
>>> regnum = create_feature_i386_32bit_segments (tdesc.get (),
>>> regnum);
>>>
>>> - if (xcr0 & X86_XSTATE_AVX)
>>> + if (xstate_bv_mask & X86_XSTATE_AVX)
>>> regnum = create_feature_i386_32bit_avx (tdesc.get (), regnum);
>>>
>>> - if (xcr0 & X86_XSTATE_AVX512)
>>> + if (xstate_bv_mask & X86_XSTATE_AVX512)
>>> regnum = create_feature_i386_32bit_avx512 (tdesc.get (), regnum);
>>>
>>> - if (xcr0 & X86_XSTATE_PKRU)
>>> + if (xstate_bv_mask & X86_XSTATE_PKRU)
>>> regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
>>>
>>> return tdesc.release ();
>>
>> <...>
>>
>>> diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c index
>>> fc7c5f642ed..a82ad21da27 100644
>>> --- a/gdb/x86-linux-nat.c
>>> +++ b/gdb/x86-linux-nat.c
>>> @@ -97,15 +97,20 @@ 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 xcr0 value is stored here and reused on subsequent calls. */
>>> - static uint64_t xcr0_storage;
>>> + 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 XSTATE_BV_STORAGE will not be configured based on
>>> + xcr0 only. */
>>
>> Something about the comment above reads off in the last phrase. Is there a
>> missing connection word?
>
> I agree, would this be a bit better?
>
> "However, once supervisor state components are supported in GDB, the value
> XSTATE_BV_STORAGE will not be configured based on xcr0 only."
>
Yes, that reads better, thanks.
I don't think you need a new iteration for these fixes though. They could be local.
>>
>>> + static uint64_t xstate_bv_storage;
>>>
>>> if (inferior_ptid == null_ptid)
>>> return this->beneath ()->read_description ();
>>>
>>> int tid = inferior_ptid.pid ();
>>>
>>> - return x86_linux_tdesc_for_tid (tid, &xcr0_storage,
>>> &this->m_xsave_layout);
>>> + return x86_linux_tdesc_for_tid (tid, &xstate_bv_storage,
>>> + &this->m_xsave_layout);
>>> }
>>>
>
>> Otherwise this looks OK to me. I suppose this was regression tested? I'd be a bit
>> more comfortable if someone more knowledgeable could chime in before this
>> gets approved.
>>
>> But if nobody shows up, we can get it in if others have no concerns.
>>
>> Reviewed-By: Luis Machado <luis.machado@arm.com>
>
> Yes, this has been regression tested (recent testing for v4 on ubuntu 24.04).
> I am currently working on the setup for testing x32 again. This configuration has been tested for v1 initially only.
Alright. Thanks for confirming.
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86.
2025-06-23 12:56 ` Luis Machado
@ 2025-06-24 13:46 ` Schimpe, Christina
2025-06-26 16:03 ` Luis Machado
0 siblings, 1 reply; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-24 13:46 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
Hi Luis,
> -----Original Message-----
> From: Luis Machado <luis.machado@arm.com>
> Sent: Monday, June 23, 2025 2:56 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> Subject: Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target
> description creation on x86.
>
> On 6/23/25 13:46, Schimpe, Christina wrote:
> > Hi Luis,
> >
> > Thanks a lot for the feedback. Please find my comments below.
> >
> >> -----Original Message-----
> >> From: Luis Machado <luis.machado@arm.com>
> >> Sent: Thursday, June 19, 2025 11:24 AM
> >> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> >> patches@sourceware.org
> >> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> >> Subject: Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for
> >> target description creation on x86.
> >>
> >> On 6/17/25 13:11, Christina Schimpe wrote:
> >>> diff --git a/gdb/arch/amd64.c b/gdb/arch/amd64.c index
> >>> 252650b6390..9cac0645cdc 100644
> >>> --- a/gdb/arch/amd64.c
> >>> +++ b/gdb/arch/amd64.c
> >>> @@ -30,14 +30,11 @@
> >>>
> >>> #include "../features/i386/x32-core.c"
> >>>
> >>> -/* Create amd64 target descriptions according to XCR0. If IS_X32 is
> >>> - true, create the x32 ones. If IS_LINUX is true, create target
> >>> - descriptions for Linux. If SEGMENTS is true, then include
> >>> - the "org.gnu.gdb.i386.segments" feature registers. */
> >>> +/* See amd64.h. */
> >>
> >> More appropriate to reference arch/amd64.h.
> >
> > True, will fix.
> >
> >>
> >>>
> >>> target_desc *
> >>> -amd64_create_target_description (uint64_t xcr0, bool is_x32, bool
> is_linux,
> >>> - bool segments)
> >>> +amd64_create_target_description (uint64_t xstate_bv_mask, bool is_x32,
> >>> + bool is_linux, bool segments)
> >>> {
> >>> target_desc_up tdesc = allocate_target_description ();
> >>>
> >>> @@ -62,13 +59,13 @@ amd64_create_target_description (uint64_t xcr0,
> >> bool is_x32, bool is_linux,
> >>> if (segments)
> >>> regnum = create_feature_i386_64bit_segments (tdesc.get (),
> >>> regnum);
> >>>
> >>> - if (xcr0 & X86_XSTATE_AVX)
> >>> + if (xstate_bv_mask & X86_XSTATE_AVX)
> >>> regnum = create_feature_i386_64bit_avx (tdesc.get (), regnum);
> >>>
> >>> - if (xcr0 & X86_XSTATE_AVX512)
> >>> + if (xstate_bv_mask & X86_XSTATE_AVX512)
> >>> regnum = create_feature_i386_64bit_avx512 (tdesc.get (),
> >>> regnum);
> >>>
> >>> - if (xcr0 & X86_XSTATE_PKRU)
> >>> + if (xstate_bv_mask & X86_XSTATE_PKRU)
> >>> regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
> >>>
> >>> return tdesc.release ();
> >>
> >> <...>
> >>
> >>> diff --git a/gdb/arch/i386.c b/gdb/arch/i386.c index
> >>> 835df53c75d..84f31439e7a 100644
> >>> --- a/gdb/arch/i386.c
> >>> +++ b/gdb/arch/i386.c
> >>> @@ -29,10 +29,11 @@
> >>> #include "../features/i386/32bit-segments.c"
> >>> #include "../features/i386/pkeys.c"
> >>>
> >>> -/* Create i386 target descriptions according to XCR0. */
> >>> +/* See i386.h. */
> >>
> >> arch/i386.h
> >
> > Will fix.
> >
> >>
> >>>
> >>> target_desc *
> >>> -i386_create_target_description (uint64_t xcr0, bool is_linux, bool
> >>> segments)
> >>> +i386_create_target_description (uint64_t xstate_bv_mask, bool is_linux,
> >>> + bool segments)
> >>> {
> >>> target_desc_up tdesc = allocate_target_description ();
> >>>
> >>> @@ -44,10 +45,10 @@ i386_create_target_description (uint64_t xcr0,
> >>> bool is_linux, bool segments)
> >>>
> >>> long regnum = 0;
> >>>
> >>> - if (xcr0 & X86_XSTATE_X87)
> >>> + if (xstate_bv_mask & X86_XSTATE_X87)
> >>> regnum = create_feature_i386_32bit_core (tdesc.get (), regnum);
> >>>
> >>> - if (xcr0 & X86_XSTATE_SSE)
> >>> + if (xstate_bv_mask & X86_XSTATE_SSE)
> >>> regnum = create_feature_i386_32bit_sse (tdesc.get (), regnum);
> >>>
> >>> if (is_linux)
> >>> @@ -56,13 +57,13 @@ i386_create_target_description (uint64_t xcr0,
> >>> bool
> >> is_linux, bool segments)
> >>> if (segments)
> >>> regnum = create_feature_i386_32bit_segments (tdesc.get (),
> >>> regnum);
> >>>
> >>> - if (xcr0 & X86_XSTATE_AVX)
> >>> + if (xstate_bv_mask & X86_XSTATE_AVX)
> >>> regnum = create_feature_i386_32bit_avx (tdesc.get (), regnum);
> >>>
> >>> - if (xcr0 & X86_XSTATE_AVX512)
> >>> + if (xstate_bv_mask & X86_XSTATE_AVX512)
> >>> regnum = create_feature_i386_32bit_avx512 (tdesc.get (),
> >>> regnum);
> >>>
> >>> - if (xcr0 & X86_XSTATE_PKRU)
> >>> + if (xstate_bv_mask & X86_XSTATE_PKRU)
> >>> regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
> >>>
> >>> return tdesc.release ();
> >>
> >> <...>
> >>
> >>> diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c index
> >>> fc7c5f642ed..a82ad21da27 100644
> >>> --- a/gdb/x86-linux-nat.c
> >>> +++ b/gdb/x86-linux-nat.c
> >>> @@ -97,15 +97,20 @@ 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 xcr0 value is stored here and reused on subsequent calls. */
> >>> - static uint64_t xcr0_storage;
> >>> + 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 XSTATE_BV_STORAGE will not be configured based
> on
> >>> + xcr0 only. */
> >>
> >> Something about the comment above reads off in the last phrase. Is
> >> there a missing connection word?
> >
> > I agree, would this be a bit better?
> >
> > "However, once supervisor state components are supported in GDB, the
> > value XSTATE_BV_STORAGE will not be configured based on xcr0 only."
> >
>
> Yes, that reads better, thanks.
>
> I don't think you need a new iteration for these fixes though. They could be
> local.
Just to be sure, you mean for this patch or your comments on the series in general?
> >>
> >>> + static uint64_t xstate_bv_storage;
> >>>
> >>> if (inferior_ptid == null_ptid)
> >>> return this->beneath ()->read_description ();
> >>>
> >>> int tid = inferior_ptid.pid ();
> >>>
> >>> - return x86_linux_tdesc_for_tid (tid, &xcr0_storage,
> >>> &this->m_xsave_layout);
> >>> + return x86_linux_tdesc_for_tid (tid, &xstate_bv_storage,
> >>> + &this->m_xsave_layout);
> >>> }
> >>>
> >
> >> Otherwise this looks OK to me. I suppose this was regression tested?
> >> I'd be a bit more comfortable if someone more knowledgeable could
> >> chime in before this gets approved.
> >>
> >> But if nobody shows up, we can get it in if others have no concerns.
> >>
> >> Reviewed-By: Luis Machado <luis.machado@arm.com>
> >
> > Yes, this has been regression tested (recent testing for v4 on ubuntu 24.04).
> > I am currently working on the setup for testing x32 again. This configuration
> has been tested for v1 initially only.
>
> Alright. Thanks for confirming.
Thanks,
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86.
2025-06-24 13:46 ` Schimpe, Christina
@ 2025-06-26 16:03 ` Luis Machado
0 siblings, 0 replies; 53+ messages in thread
From: Luis Machado @ 2025-06-26 16:03 UTC (permalink / raw)
To: Schimpe, Christina, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/24/25 14:46, Schimpe, Christina wrote:
> Hi Luis,
>
>> -----Original Message-----
>> From: Luis Machado <luis.machado@arm.com>
>> Sent: Monday, June 23, 2025 2:56 PM
>> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>> patches@sourceware.org
>> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
>> Subject: Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target
>> description creation on x86.
>>
>> On 6/23/25 13:46, Schimpe, Christina wrote:
>>> Hi Luis,
>>>
>>> Thanks a lot for the feedback. Please find my comments below.
>>>
>>>> -----Original Message-----
>>>> From: Luis Machado <luis.machado@arm.com>
>>>> Sent: Thursday, June 19, 2025 11:24 AM
>>>> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>>>> patches@sourceware.org
>>>> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
>>>> Subject: Re: [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for
>>>> target description creation on x86.
>>>>
>>>> On 6/17/25 13:11, Christina Schimpe wrote:
>>>>> diff --git a/gdb/arch/amd64.c b/gdb/arch/amd64.c index
>>>>> 252650b6390..9cac0645cdc 100644
>>>>> --- a/gdb/arch/amd64.c
>>>>> +++ b/gdb/arch/amd64.c
>>>>> @@ -30,14 +30,11 @@
>>>>>
>>>>> #include "../features/i386/x32-core.c"
>>>>>
>>>>> -/* Create amd64 target descriptions according to XCR0. If IS_X32 is
>>>>> - true, create the x32 ones. If IS_LINUX is true, create target
>>>>> - descriptions for Linux. If SEGMENTS is true, then include
>>>>> - the "org.gnu.gdb.i386.segments" feature registers. */
>>>>> +/* See amd64.h. */
>>>>
>>>> More appropriate to reference arch/amd64.h.
>>>
>>> True, will fix.
>>>
>>>>
>>>>>
>>>>> target_desc *
>>>>> -amd64_create_target_description (uint64_t xcr0, bool is_x32, bool
>> is_linux,
>>>>> - bool segments)
>>>>> +amd64_create_target_description (uint64_t xstate_bv_mask, bool is_x32,
>>>>> + bool is_linux, bool segments)
>>>>> {
>>>>> target_desc_up tdesc = allocate_target_description ();
>>>>>
>>>>> @@ -62,13 +59,13 @@ amd64_create_target_description (uint64_t xcr0,
>>>> bool is_x32, bool is_linux,
>>>>> if (segments)
>>>>> regnum = create_feature_i386_64bit_segments (tdesc.get (),
>>>>> regnum);
>>>>>
>>>>> - if (xcr0 & X86_XSTATE_AVX)
>>>>> + if (xstate_bv_mask & X86_XSTATE_AVX)
>>>>> regnum = create_feature_i386_64bit_avx (tdesc.get (), regnum);
>>>>>
>>>>> - if (xcr0 & X86_XSTATE_AVX512)
>>>>> + if (xstate_bv_mask & X86_XSTATE_AVX512)
>>>>> regnum = create_feature_i386_64bit_avx512 (tdesc.get (),
>>>>> regnum);
>>>>>
>>>>> - if (xcr0 & X86_XSTATE_PKRU)
>>>>> + if (xstate_bv_mask & X86_XSTATE_PKRU)
>>>>> regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
>>>>>
>>>>> return tdesc.release ();
>>>>
>>>> <...>
>>>>
>>>>> diff --git a/gdb/arch/i386.c b/gdb/arch/i386.c index
>>>>> 835df53c75d..84f31439e7a 100644
>>>>> --- a/gdb/arch/i386.c
>>>>> +++ b/gdb/arch/i386.c
>>>>> @@ -29,10 +29,11 @@
>>>>> #include "../features/i386/32bit-segments.c"
>>>>> #include "../features/i386/pkeys.c"
>>>>>
>>>>> -/* Create i386 target descriptions according to XCR0. */
>>>>> +/* See i386.h. */
>>>>
>>>> arch/i386.h
>>>
>>> Will fix.
>>>
>>>>
>>>>>
>>>>> target_desc *
>>>>> -i386_create_target_description (uint64_t xcr0, bool is_linux, bool
>>>>> segments)
>>>>> +i386_create_target_description (uint64_t xstate_bv_mask, bool is_linux,
>>>>> + bool segments)
>>>>> {
>>>>> target_desc_up tdesc = allocate_target_description ();
>>>>>
>>>>> @@ -44,10 +45,10 @@ i386_create_target_description (uint64_t xcr0,
>>>>> bool is_linux, bool segments)
>>>>>
>>>>> long regnum = 0;
>>>>>
>>>>> - if (xcr0 & X86_XSTATE_X87)
>>>>> + if (xstate_bv_mask & X86_XSTATE_X87)
>>>>> regnum = create_feature_i386_32bit_core (tdesc.get (), regnum);
>>>>>
>>>>> - if (xcr0 & X86_XSTATE_SSE)
>>>>> + if (xstate_bv_mask & X86_XSTATE_SSE)
>>>>> regnum = create_feature_i386_32bit_sse (tdesc.get (), regnum);
>>>>>
>>>>> if (is_linux)
>>>>> @@ -56,13 +57,13 @@ i386_create_target_description (uint64_t xcr0,
>>>>> bool
>>>> is_linux, bool segments)
>>>>> if (segments)
>>>>> regnum = create_feature_i386_32bit_segments (tdesc.get (),
>>>>> regnum);
>>>>>
>>>>> - if (xcr0 & X86_XSTATE_AVX)
>>>>> + if (xstate_bv_mask & X86_XSTATE_AVX)
>>>>> regnum = create_feature_i386_32bit_avx (tdesc.get (), regnum);
>>>>>
>>>>> - if (xcr0 & X86_XSTATE_AVX512)
>>>>> + if (xstate_bv_mask & X86_XSTATE_AVX512)
>>>>> regnum = create_feature_i386_32bit_avx512 (tdesc.get (),
>>>>> regnum);
>>>>>
>>>>> - if (xcr0 & X86_XSTATE_PKRU)
>>>>> + if (xstate_bv_mask & X86_XSTATE_PKRU)
>>>>> regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
>>>>>
>>>>> return tdesc.release ();
>>>>
>>>> <...>
>>>>
>>>>> diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c index
>>>>> fc7c5f642ed..a82ad21da27 100644
>>>>> --- a/gdb/x86-linux-nat.c
>>>>> +++ b/gdb/x86-linux-nat.c
>>>>> @@ -97,15 +97,20 @@ 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 xcr0 value is stored here and reused on subsequent calls. */
>>>>> - static uint64_t xcr0_storage;
>>>>> + 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 XSTATE_BV_STORAGE will not be configured based
>> on
>>>>> + xcr0 only. */
>>>>
>>>> Something about the comment above reads off in the last phrase. Is
>>>> there a missing connection word?
>>>
>>> I agree, would this be a bit better?
>>>
>>> "However, once supervisor state components are supported in GDB, the
>>> value XSTATE_BV_STORAGE will not be configured based on xcr0 only."
>>>
>>
>> Yes, that reads better, thanks.
>>
>> I don't think you need a new iteration for these fixes though. They could be
>> local.
>
> Just to be sure, you mean for this patch or your comments on the series in general?
>
I meant on this patch. Sorry, missed this reply.
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
` (3 preceding siblings ...)
2025-06-17 12:11 ` [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86 Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-17 12:20 ` Eli Zaretskii
2025-06-19 9:24 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 06/11] gdb: amd64 linux coredump support with shadow stack Christina Schimpe
` (5 subsequent siblings)
10 siblings, 2 replies; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
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.
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
gdb/NEWS | 3 +
gdb/amd64-linux-nat.c | 17 +++++
gdb/amd64-linux-tdep.c | 1 +
gdb/amd64-tdep.c | 6 +-
gdb/amd64-tdep.h | 1 +
gdb/arch/amd64.c | 10 +++
gdb/arch/i386.c | 4 ++
gdb/arch/x86-linux-tdesc-features.c | 1 +
gdb/doc/gdb.texinfo | 4 ++
gdb/features/Makefile | 2 +
gdb/features/i386/32bit-ssp.c | 14 ++++
gdb/features/i386/32bit-ssp.xml | 11 +++
gdb/features/i386/64bit-ssp.c | 14 ++++
gdb/features/i386/64bit-ssp.xml | 11 +++
gdb/i386-tdep.c | 22 +++++-
gdb/i386-tdep.h | 4 ++
gdb/nat/x86-linux-tdesc.c | 2 +
gdb/nat/x86-linux.c | 55 +++++++++++++++
gdb/nat/x86-linux.h | 4 ++
gdb/testsuite/gdb.arch/amd64-shadow-stack.c | 22 ++++++
gdb/testsuite/gdb.arch/amd64-ssp.exp | 50 +++++++++++++
.../gdb.base/inline-frame-cycle-unwind.py | 4 ++
gdb/testsuite/lib/gdb.exp | 70 +++++++++++++++++++
gdb/x86-linux-nat.c | 50 +++++++++++--
gdb/x86-linux-nat.h | 11 +++
gdb/x86-tdep.c | 21 ++++++
gdb/x86-tdep.h | 9 +++
gdbserver/linux-x86-low.cc | 28 +++++++-
gdbsupport/x86-xstate.h | 5 +-
29 files changed, 446 insertions(+), 10 deletions(-)
create mode 100644 gdb/features/i386/32bit-ssp.c
create mode 100644 gdb/features/i386/32bit-ssp.xml
create mode 100644 gdb/features/i386/64bit-ssp.c
create mode 100644 gdb/features/i386/64bit-ssp.xml
create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack.c
create mode 100644 gdb/testsuite/gdb.arch/amd64-ssp.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 4dcc344b072..b8fb7f0e484 100644
--- 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.
diff --git a/gdb/amd64-linux-nat.c b/gdb/amd64-linux-nat.c
index 75e63c67ba0..0058f910acf 100644
--- a/gdb/amd64-linux-nat.c
+++ b/gdb/amd64-linux-nat.c
@@ -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 > 0)
+ || (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 > 0)
+ || (regnum != -1 && regnum == tdep->ssp_regnum))
+ {
+ x86_linux_store_ssp (regcache, tid);
+ if (regnum != -1)
+ return;
+ }
+
struct iovec iov;
iov.iov_base = xstateregs.data ();
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index e5a2ab9dd52..f23db4dce22 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -107,6 +107,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. */
diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c
index e4a0ecf7ac9..79f7e427841 100644
--- a/gdb/amd64-tdep.c
+++ b/gdb/amd64-tdep.c
@@ -3367,6 +3367,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;
@@ -3529,12 +3532,13 @@ const struct target_desc *
amd64_target_description (uint64_t xstate_bv_mask, 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_mask & X86_XSTATE_AVX) ? 1 : 0]
[(xstate_bv_mask & X86_XSTATE_AVX512) ? 1 : 0]
[(xstate_bv_mask & X86_XSTATE_PKRU) ? 1 : 0]
+ [(xstate_bv_mask & X86_XSTATE_CET_U) ? 1 : 0]
[segments ? 1 : 0];
if (*tdesc == NULL)
diff --git a/gdb/amd64-tdep.h b/gdb/amd64-tdep.h
index 82f781bf404..eb294f3dfbe 100644
--- a/gdb/amd64-tdep.h
+++ b/gdb/amd64-tdep.h
@@ -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
};
diff --git a/gdb/arch/amd64.c b/gdb/arch/amd64.c
index 9cac0645cdc..1abb885cc6d 100644
--- a/gdb/arch/amd64.c
+++ b/gdb/arch/amd64.c
@@ -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 amd64.h. */
@@ -68,5 +70,13 @@ amd64_create_target_description (uint64_t xstate_bv_mask, bool is_x32,
if (xstate_bv_mask & X86_XSTATE_PKRU)
regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
+ if (xstate_bv_mask & 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 ();
}
diff --git a/gdb/arch/i386.c b/gdb/arch/i386.c
index 84f31439e7a..7597791a88d 100644
--- a/gdb/arch/i386.c
+++ b/gdb/arch/i386.c
@@ -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 i386.h. */
@@ -66,5 +67,8 @@ i386_create_target_description (uint64_t xstate_bv_mask, bool is_linux,
if (xstate_bv_mask & X86_XSTATE_PKRU)
regnum = create_feature_i386_pkeys (tdesc.get (), regnum);
+ if (xstate_bv_mask & X86_XSTATE_CET_U)
+ regnum = create_feature_i386_32bit_ssp (tdesc.get (), regnum);
+
return tdesc.release ();
}
diff --git a/gdb/arch/x86-linux-tdesc-features.c b/gdb/arch/x86-linux-tdesc-features.c
index 68f37fccaef..f288f120cfb 100644
--- a/gdb/arch/x86-linux-tdesc-features.c
+++ b/gdb/arch/x86-linux-tdesc-features.c
@@ -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 },
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index c41aa89a3c4..0ae09f09c88 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -49938,6 +49938,10 @@ 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. Following the
+restriction of the Linux kernel, only amd64 is supported for now.
+
@node LoongArch Features
@subsection LoongArch Features
@cindex target descriptions, LoongArch Features
diff --git a/gdb/features/Makefile b/gdb/features/Makefile
index 7a8c7999733..2afda1ccd00 100644
--- a/gdb/features/Makefile
+++ b/gdb/features/Makefile
@@ -225,6 +225,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 \
@@ -232,6 +233,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
index 00000000000..991bae3c1e6
--- /dev/null
+++ b/gdb/features/i386/32bit-ssp.c
@@ -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
index 00000000000..d17e7004eec
--- /dev/null
+++ b/gdb/features/i386/32bit-ssp.xml
@@ -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
index 00000000000..5468099ddf6
--- /dev/null
+++ b/gdb/features/i386/64bit-ssp.c
@@ -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
index 00000000000..a0688d018a5
--- /dev/null
+++ b/gdb/features/i386/64bit-ssp.xml
@@ -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>
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index eca462f0f1b..f3fa4e511e6 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -8403,7 +8403,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))
@@ -8429,6 +8430,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. */
@@ -8544,6 +8548,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;
}
@@ -8835,6 +8848,9 @@ i386_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
/* No segment base registers. */
tdep->fsbase_regnum = -1;
+ /* No shadow stack pointer register. */
+ tdep->ssp_regnum = -1;
+
tdesc_arch_data_up tdesc_data = tdesc_data_alloc ();
set_gdbarch_relocate_instruction (gdbarch, i386_relocate_instruction);
@@ -8955,13 +8971,15 @@ const struct target_desc *
i386_target_description (uint64_t xstate_bv_mask, 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_mask & X86_XSTATE_SSE) ? 1 : 0]
[(xstate_bv_mask & X86_XSTATE_AVX) ? 1 : 0]
[(xstate_bv_mask & X86_XSTATE_AVX512) ? 1 : 0]
[(xstate_bv_mask & X86_XSTATE_PKRU) ? 1 : 0]
+ [(xstate_bv_mask & X86_XSTATE_CET_U) ? 1 : 0]
[segments ? 1 : 0];
if (*tdesc == NULL)
diff --git a/gdb/i386-tdep.h b/gdb/i386-tdep.h
index 239bc8674e8..60d6f3eb732 100644
--- a/gdb/i386-tdep.h
+++ b/gdb/i386-tdep.h
@@ -191,6 +191,9 @@ struct i386_gdbarch_tdep : gdbarch_tdep_base
/* PKEYS register names. */
const char * const *pkeys_register_names = nullptr;
+ /* Shadow stack pointer register. */
+ int ssp_regnum = 0;
+
/* Register number for %fsbase. Set this to -1 to indicate the
absence of segment base registers. */
int fsbase_regnum = 0;
@@ -293,6 +296,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
};
diff --git a/gdb/nat/x86-linux-tdesc.c b/gdb/nat/x86-linux-tdesc.c
index e9cf2527c5f..5bc36b6bef2 100644
--- a/gdb/nat/x86-linux-tdesc.c
+++ b/gdb/nat/x86-linux-tdesc.c
@@ -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;
}
}
diff --git a/gdb/nat/x86-linux.c b/gdb/nat/x86-linux.c
index 0bdff736f8a..bf603182164 100644
--- a/gdb/nat/x86-linux.c
+++ b/gdb/nat/x86-linux.c
@@ -17,6 +17,12 @@
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,52 @@ x86_linux_ptrace_get_arch_size (int tid)
return x86_linux_arch_size (false, false);
#endif
}
+
+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;
+
+ __get_cpuid_count (7, 0, &eax, &ebx, &ecx, &edx);
+
+ 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;
+}
diff --git a/gdb/nat/x86-linux.h b/gdb/nat/x86-linux.h
index dbdef081515..1783aae05d0 100644
--- a/gdb/nat/x86-linux.h
+++ b/gdb/nat/x86-linux.h
@@ -75,4 +75,8 @@ struct x86_linux_arch_size
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
index 00000000000..643ef2d5f56
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack.c
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ 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/>. */
+
+int
+main ()
+{
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/amd64-ssp.exp b/gdb/testsuite/gdb.arch/amd64-ssp.exp
new file mode 100644
index 00000000000..6ddc875b9a3
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-ssp.exp
@@ -0,0 +1,50 @@
+# 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 accessing the shadow stack pointer register.
+
+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} \
+ additional_flags="-fcf-protection=return"] } {
+ return -1
+ }
+
+ if {![runto_main]} {
+ return -1
+ }
+
+ # Read PL3_SSP register.
+ set ssp_main [get_hexadecimal_valueof "\$pl3_ssp" "read pl3_ssp value"]
+
+ # Write PL3_SSP register.
+ 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"
+
+ # Restore original value.
+ gdb_test "print /x \$pl3_ssp = $ssp_main" "= $ssp_main" "restore original pl3_ssp"
+
+ # Potential CET violations often only occur after resuming normal execution.
+ # Therefore, it is important to test normal program continuation after
+ # configuring the shadow stack pointer.
+ gdb_continue_to_end
+}
+
diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
index bc4a673b847..654ff447523 100644
--- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
+++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
@@ -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
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 3f1cd55d727..7e56cf61a4c 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -4287,6 +4287,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.
diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c
index a82ad21da27..865c017404e 100644
--- a/gdb/x86-linux-nat.c
+++ b/gdb/x86-linux-nat.c
@@ -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 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,46 @@ 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);
+
+ /* Starting with v6.6., the Linux kernel supports CET shadow stack.
+ 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"));
+}
+
void _initialize_x86_linux_nat ();
void
_initialize_x86_linux_nat ()
diff --git a/gdb/x86-linux-nat.h b/gdb/x86-linux-nat.h
index a62cc4d7ac0..c4556532226 100644
--- a/gdb/x86-linux-nat.h
+++ b/gdb/x86-linux-nat.h
@@ -92,4 +92,15 @@ struct x86_linux_nat_target : public x86_nat_target<linux_nat_target>
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 */
diff --git a/gdb/x86-tdep.c b/gdb/x86-tdep.c
index 6646b1157c8..06dec4e1f90 100644
--- a/gdb/x86-tdep.c
+++ b/gdb/x86-tdep.c
@@ -17,10 +17,31 @@
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 > 0);
+ 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 > 0);
+ regcache->raw_collect (tdep->ssp_regnum, &ssp);
+}
+
/* Check whether NAME is included in NAMES[LO] (inclusive) to NAMES[HI]
(exclusive). */
diff --git a/gdb/x86-tdep.h b/gdb/x86-tdep.h
index 35e3905f2e3..53c3956ea5e 100644
--- a/gdb/x86-tdep.h
+++ b/gdb/x86-tdep.h
@@ -20,6 +20,15 @@
#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). */
diff --git a/gdbserver/linux-x86-low.cc b/gdbserver/linux-x86-low.cc
index dbc11d6856d..ec7064c2834 100644
--- a/gdbserver/linux-x86-low.cc
+++ b/gdbserver/linux-x86-low.cc
@@ -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.");
}
diff --git a/gdbsupport/x86-xstate.h b/gdbsupport/x86-xstate.h
index 9bb373c3100..6657c450c88 100644
--- a/gdbsupport/x86-xstate.h
+++ b/gdbsupport/x86-xstate.h
@@ -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
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register.
2025-06-17 12:11 ` [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register Christina Schimpe
@ 2025-06-17 12:20 ` Eli Zaretskii
2025-06-19 9:24 ` Luis Machado
1 sibling, 0 replies; 53+ messages in thread
From: Eli Zaretskii @ 2025-06-17 12:20 UTC (permalink / raw)
To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann
> From: Christina Schimpe <christina.schimpe@intel.com>
> Cc: thiago.bauermann@linaro.org,
> eliz@gnu.org
> Date: Tue, 17 Jun 2025 12:11:41 +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.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> gdb/NEWS | 3 +
> gdb/amd64-linux-nat.c | 17 +++++
> gdb/amd64-linux-tdep.c | 1 +
> gdb/amd64-tdep.c | 6 +-
> gdb/amd64-tdep.h | 1 +
> gdb/arch/amd64.c | 10 +++
> gdb/arch/i386.c | 4 ++
> gdb/arch/x86-linux-tdesc-features.c | 1 +
> gdb/doc/gdb.texinfo | 4 ++
> gdb/features/Makefile | 2 +
> gdb/features/i386/32bit-ssp.c | 14 ++++
> gdb/features/i386/32bit-ssp.xml | 11 +++
> gdb/features/i386/64bit-ssp.c | 14 ++++
> gdb/features/i386/64bit-ssp.xml | 11 +++
> gdb/i386-tdep.c | 22 +++++-
> gdb/i386-tdep.h | 4 ++
> gdb/nat/x86-linux-tdesc.c | 2 +
> gdb/nat/x86-linux.c | 55 +++++++++++++++
> gdb/nat/x86-linux.h | 4 ++
> gdb/testsuite/gdb.arch/amd64-shadow-stack.c | 22 ++++++
> gdb/testsuite/gdb.arch/amd64-ssp.exp | 50 +++++++++++++
> .../gdb.base/inline-frame-cycle-unwind.py | 4 ++
> gdb/testsuite/lib/gdb.exp | 70 +++++++++++++++++++
> gdb/x86-linux-nat.c | 50 +++++++++++--
> gdb/x86-linux-nat.h | 11 +++
> gdb/x86-tdep.c | 21 ++++++
> gdb/x86-tdep.h | 9 +++
> gdbserver/linux-x86-low.cc | 28 +++++++-
> gdbsupport/x86-xstate.h | 5 +-
> 29 files changed, 446 insertions(+), 10 deletions(-)
> create mode 100644 gdb/features/i386/32bit-ssp.c
> create mode 100644 gdb/features/i386/32bit-ssp.xml
> create mode 100644 gdb/features/i386/64bit-ssp.c
> create mode 100644 gdb/features/i386/64bit-ssp.xml
> create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack.c
> create mode 100644 gdb/testsuite/gdb.arch/amd64-ssp.exp
The NEWS part is okay, thanks.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register.
2025-06-17 12:11 ` [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register Christina Schimpe
2025-06-17 12:20 ` Eli Zaretskii
@ 2025-06-19 9:24 ` Luis Machado
2025-06-23 13:05 ` Schimpe, Christina
1 sibling, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:24 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> 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.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> gdb/NEWS | 3 +
> gdb/amd64-linux-nat.c | 17 +++++
> gdb/amd64-linux-tdep.c | 1 +
> gdb/amd64-tdep.c | 6 +-
> gdb/amd64-tdep.h | 1 +
> gdb/arch/amd64.c | 10 +++
> gdb/arch/i386.c | 4 ++
> gdb/arch/x86-linux-tdesc-features.c | 1 +
> gdb/doc/gdb.texinfo | 4 ++
> gdb/features/Makefile | 2 +
> gdb/features/i386/32bit-ssp.c | 14 ++++
> gdb/features/i386/32bit-ssp.xml | 11 +++
> gdb/features/i386/64bit-ssp.c | 14 ++++
> gdb/features/i386/64bit-ssp.xml | 11 +++
> gdb/i386-tdep.c | 22 +++++-
> gdb/i386-tdep.h | 4 ++
> gdb/nat/x86-linux-tdesc.c | 2 +
> gdb/nat/x86-linux.c | 55 +++++++++++++++
> gdb/nat/x86-linux.h | 4 ++
> gdb/testsuite/gdb.arch/amd64-shadow-stack.c | 22 ++++++
> gdb/testsuite/gdb.arch/amd64-ssp.exp | 50 +++++++++++++
> .../gdb.base/inline-frame-cycle-unwind.py | 4 ++
> gdb/testsuite/lib/gdb.exp | 70 +++++++++++++++++++
> gdb/x86-linux-nat.c | 50 +++++++++++--
> gdb/x86-linux-nat.h | 11 +++
> gdb/x86-tdep.c | 21 ++++++
> gdb/x86-tdep.h | 9 +++
> gdbserver/linux-x86-low.cc | 28 +++++++-
> gdbsupport/x86-xstate.h | 5 +-
> 29 files changed, 446 insertions(+), 10 deletions(-)
> create mode 100644 gdb/features/i386/32bit-ssp.c
> create mode 100644 gdb/features/i386/32bit-ssp.xml
> create mode 100644 gdb/features/i386/64bit-ssp.c
> create mode 100644 gdb/features/i386/64bit-ssp.xml
> create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack.c
> create mode 100644 gdb/testsuite/gdb.arch/amd64-ssp.exp
>
<...>
> diff --git a/gdb/nat/x86-linux.c b/gdb/nat/x86-linux.c
> index 0bdff736f8a..bf603182164 100644
> --- a/gdb/nat/x86-linux.c
> +++ b/gdb/nat/x86-linux.c
> @@ -17,6 +17,12 @@
> 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,52 @@ x86_linux_ptrace_get_arch_size (int tid)
> return x86_linux_arch_size (false, false);
> #endif
> }
> +
> +bool
> +x86_check_ssp_support (const int tid)
Missing the usual comment pointing at the header file?
/* See nat/x86-linux.h. */
> +{
> + /* 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;
> +
> + __get_cpuid_count (7, 0, &eax, &ebx, &ecx, &edx);
> +
> + 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;
> +}
<...>
> diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> index 3f1cd55d727..7e56cf61a4c 100644
> --- a/gdb/testsuite/lib/gdb.exp
> +++ b/gdb/testsuite/lib/gdb.exp
> @@ -4287,6 +4287,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.
Small typo, period after v6.6.
> + # 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.
>
> diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c
> index a82ad21da27..865c017404e 100644
> --- a/gdb/x86-linux-nat.c
> +++ b/gdb/x86-linux-nat.c
> @@ -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 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,46 @@ 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);
> +
> + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
Same typo, period after v6.6
> + 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"));
> +}
> +
> void _initialize_x86_linux_nat ();
> void
> _initialize_x86_linux_nat ()
<...>
Other than the cosmetic points above, the patch looks OK to me. I'm not entirely familiar
with the feature for x86, but the enablement makes sense to me.
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register.
2025-06-19 9:24 ` Luis Machado
@ 2025-06-23 13:05 ` Schimpe, Christina
0 siblings, 0 replies; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-23 13:05 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
Hi Luis,
Thanks a lot for the review.
I will fix your comments asap.
Christina
> -----Original Message-----
> From: Luis Machado <luis.machado@arm.com>
> Sent: Thursday, June 19, 2025 11:24 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> Subject: Re: [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack
> pointer register.
>
> On 6/17/25 13:11, Christina Schimpe wrote:
> > 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.
> >
> > Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> > Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> > ---
> > gdb/NEWS | 3 +
> > gdb/amd64-linux-nat.c | 17 +++++
> > gdb/amd64-linux-tdep.c | 1 +
> > gdb/amd64-tdep.c | 6 +-
> > gdb/amd64-tdep.h | 1 +
> > gdb/arch/amd64.c | 10 +++
> > gdb/arch/i386.c | 4 ++
> > gdb/arch/x86-linux-tdesc-features.c | 1 +
> > gdb/doc/gdb.texinfo | 4 ++
> > gdb/features/Makefile | 2 +
> > gdb/features/i386/32bit-ssp.c | 14 ++++
> > gdb/features/i386/32bit-ssp.xml | 11 +++
> > gdb/features/i386/64bit-ssp.c | 14 ++++
> > gdb/features/i386/64bit-ssp.xml | 11 +++
> > gdb/i386-tdep.c | 22 +++++-
> > gdb/i386-tdep.h | 4 ++
> > gdb/nat/x86-linux-tdesc.c | 2 +
> > gdb/nat/x86-linux.c | 55 +++++++++++++++
> > gdb/nat/x86-linux.h | 4 ++
> > gdb/testsuite/gdb.arch/amd64-shadow-stack.c | 22 ++++++
> > gdb/testsuite/gdb.arch/amd64-ssp.exp | 50 +++++++++++++
> > .../gdb.base/inline-frame-cycle-unwind.py | 4 ++
> > gdb/testsuite/lib/gdb.exp | 70 +++++++++++++++++++
> > gdb/x86-linux-nat.c | 50 +++++++++++--
> > gdb/x86-linux-nat.h | 11 +++
> > gdb/x86-tdep.c | 21 ++++++
> > gdb/x86-tdep.h | 9 +++
> > gdbserver/linux-x86-low.cc | 28 +++++++-
> > gdbsupport/x86-xstate.h | 5 +-
> > 29 files changed, 446 insertions(+), 10 deletions(-) create mode
> > 100644 gdb/features/i386/32bit-ssp.c create mode 100644
> > gdb/features/i386/32bit-ssp.xml create mode 100644
> > gdb/features/i386/64bit-ssp.c create mode 100644
> > gdb/features/i386/64bit-ssp.xml create mode 100644
> > gdb/testsuite/gdb.arch/amd64-shadow-stack.c
> > create mode 100644 gdb/testsuite/gdb.arch/amd64-ssp.exp
> >
>
> <...>
>
> > diff --git a/gdb/nat/x86-linux.c b/gdb/nat/x86-linux.c index
> > 0bdff736f8a..bf603182164 100644
> > --- a/gdb/nat/x86-linux.c
> > +++ b/gdb/nat/x86-linux.c
> > @@ -17,6 +17,12 @@
> > 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,52 @@ x86_linux_ptrace_get_arch_size (int tid)
> > return x86_linux_arch_size (false, false); #endif }
> > +
> > +bool
> > +x86_check_ssp_support (const int tid)
>
> Missing the usual comment pointing at the header file?
>
> /* See nat/x86-linux.h. */
>
> > +{
> > + /* 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;
> > +
> > + __get_cpuid_count (7, 0, &eax, &ebx, &ecx, &edx);
> > +
> > + 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;
> > +}
>
> <...>
>
> > diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> > index 3f1cd55d727..7e56cf61a4c 100644
> > --- a/gdb/testsuite/lib/gdb.exp
> > +++ b/gdb/testsuite/lib/gdb.exp
> > @@ -4287,6 +4287,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.
>
> Small typo, period after v6.6.
>
> > + # 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.
> >
> > diff --git a/gdb/x86-linux-nat.c b/gdb/x86-linux-nat.c index
> > a82ad21da27..865c017404e 100644
> > --- a/gdb/x86-linux-nat.c
> > +++ b/gdb/x86-linux-nat.c
> > @@ -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 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,46 @@ x86_linux_get_thread_area (pid_t pid, void *addr,
> > unsigned int *base_addr) }
> >
>
>
> >
> > +/* 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);
> > +
> > + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
>
> Same typo, period after v6.6
>
> > + 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")); }
> > +
> > void _initialize_x86_linux_nat ();
> > void
> > _initialize_x86_linux_nat ()
>
> <...>
>
> Other than the cosmetic points above, the patch looks OK to me. I'm not
> entirely familiar with the feature for x86, but the enablement makes sense to
> me.
>
> Reviewed-By: Luis Machado <luis.machado@arm.com>
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
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 06/11] gdb: amd64 linux coredump support with shadow stack.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
` (4 preceding siblings ...)
2025-06-17 12:11 ` [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-19 9:24 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux Christina Schimpe
` (4 subsequent siblings)
10 siblings, 1 reply; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
From: Felix Willgerodt <felix.willgerodt@intel.com>
Intel's Control-Flow Enforcement Technology (CET) provides the shadow
stack feature for the x86 architecture.
This commit adds support to write and read the shadow-stack node in
corefiles. This helps debugging return address violations post-mortem.
The format is synced with the linux kernel commit "x86: Add PTRACE
interface for shadow stack". As the linux kernel restricts shadow
stack support to 64-bit, apply the fix for amd64 only.
Co-Authored-By: Christina Schimpe <christina.schimpe@intel.com>
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
gdb/amd64-linux-tdep.c | 57 +++++++++++++++++--
.../gdb.arch/amd64-shadow-stack-corefile.exp | 50 ++++++++++++++++
2 files changed, 103 insertions(+), 4 deletions(-)
create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index f23db4dce22..d806d3cb1f7 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -46,6 +46,7 @@
#include "expop.h"
#include "arch/amd64-linux-tdesc.h"
#include "inferior.h"
+#include "x86-tdep.h"
/* The syscall's XML filename for i386. */
#define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
@@ -1592,6 +1593,14 @@ amd64_linux_record_signal (struct gdbarch *gdbarch,
return 0;
}
+/* Get shadow stack pointer state from core dump. */
+
+static bool
+amd64_linux_core_read_ssp_state_p (bfd *abfd)
+{
+ return bfd_get_section_by_name (abfd, ".reg-ssp") != NULL;
+}
+
/* Get Linux/x86 target description from core dump. */
static const struct target_desc *
@@ -1601,11 +1610,14 @@ amd64_linux_core_read_description (struct gdbarch *gdbarch,
{
/* Linux/x86-64. */
x86_xsave_layout layout;
- uint64_t xcr0 = i386_linux_core_read_xsave_info (abfd, layout);
- if (xcr0 == 0)
- xcr0 = X86_XSTATE_SSE_MASK;
+ uint64_t xstate_bv_mask = i386_linux_core_read_xsave_info (abfd, layout);
+ if (xstate_bv_mask == 0)
+ xstate_bv_mask = X86_XSTATE_SSE_MASK;
+
+ if (amd64_linux_core_read_ssp_state_p (abfd))
+ xstate_bv_mask |= X86_XSTATE_CET_U;
- return amd64_linux_read_description (xcr0 & X86_XSTATE_ALL_MASK,
+ return amd64_linux_read_description (xstate_bv_mask & X86_XSTATE_ALL_MASK,
gdbarch_ptr_bit (gdbarch) == 32);
}
@@ -1636,6 +1648,35 @@ static const struct regset amd64_linux_xstateregset =
amd64_linux_collect_xstateregset
};
+/* Supply shadow stack pointer register from the buffer SSP to the
+ register cache REGCACHE. */
+
+static void
+amd64_linux_supply_ssp (const regset *regset,
+ regcache *regcache, int regnum,
+ const void *ssp, size_t len)
+{
+ x86_supply_ssp (regcache, *static_cast<const uint64_t *> (ssp));
+}
+
+/* Collect the shadow stack pointer register from the register cache
+ REGCACHE and store it in SSP. */
+
+static void
+amd64_linux_collect_ssp (const regset *regset,
+ const regcache *regcache, int regnum,
+ void *ssp, size_t len)
+{
+ x86_collect_ssp (regcache, *static_cast<uint64_t *> (ssp));
+}
+
+/* Shadow stack pointer register. */
+
+static const struct regset amd64_linux_ssp_register
+ {
+ NULL, amd64_linux_supply_ssp, amd64_linux_collect_ssp
+ };
+
/* Iterate over core file register note sections. */
static void
@@ -1652,6 +1693,14 @@ amd64_linux_iterate_over_regset_sections (struct gdbarch *gdbarch,
cb (".reg-xstate", tdep->xsave_layout.sizeof_xsave,
tdep->xsave_layout.sizeof_xsave, &amd64_linux_xstateregset,
"XSAVE extended state", cb_data);
+
+ /* SSP can be unavailable. Thus, we need to check the register status
+ in case we write a core file (regcache != nullptr). */
+ if (tdep->ssp_regnum > 0
+ && (regcache == nullptr
+ || REG_VALID == regcache->get_register_status (tdep->ssp_regnum)))
+ cb (".reg-ssp", 8, 8, &amd64_linux_ssp_register,
+ "shadow stack pointer", cb_data);
}
/* The instruction sequences used in x86_64 machines for a
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
new file mode 100644
index 00000000000..25cc1529f0d
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
@@ -0,0 +1,50 @@
+# Copyright 2021-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 the shadow stack pointer note in core dumps.
+
+require allow_ssp_tests
+
+standard_testfile amd64-shadow-stack.c
+set gcorefile ${binfile}.gcore
+
+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 -1
+ }
+
+ if { ![runto_main] } {
+ return -1
+ }
+
+ # Save ssp for comparison in the corefile session.
+ set ssp [get_hexadecimal_valueof "\$pl3_ssp" ""]
+
+ if { ![gdb_gcore_cmd $gcorefile "save a corefile"] } {
+ return -1
+ }
+
+ # Now restart gdb and load the corefile.
+ clean_restart ${binfile}
+
+ gdb_test "core ${gcorefile}" \
+ "Core was generated by .*" "re-load generated corefile"
+
+ gdb_test "print /x \$pl3_ssp" "= $ssp"
+}
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 06/11] gdb: amd64 linux coredump support with shadow stack.
2025-06-17 12:11 ` [PATCH v4 06/11] gdb: amd64 linux coredump support with shadow stack Christina Schimpe
@ 2025-06-19 9:24 ` Luis Machado
2025-06-23 13:16 ` Schimpe, Christina
0 siblings, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:24 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> From: Felix Willgerodt <felix.willgerodt@intel.com>
>
> Intel's Control-Flow Enforcement Technology (CET) provides the shadow
> stack feature for the x86 architecture.
>
> This commit adds support to write and read the shadow-stack node in
> corefiles. This helps debugging return address violations post-mortem.
> The format is synced with the linux kernel commit "x86: Add PTRACE
> interface for shadow stack". As the linux kernel restricts shadow
> stack support to 64-bit, apply the fix for amd64 only.
>
> Co-Authored-By: Christina Schimpe <christina.schimpe@intel.com>
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
> gdb/amd64-linux-tdep.c | 57 +++++++++++++++++--
> .../gdb.arch/amd64-shadow-stack-corefile.exp | 50 ++++++++++++++++
> 2 files changed, 103 insertions(+), 4 deletions(-)
> create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
>
> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> index f23db4dce22..d806d3cb1f7 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -46,6 +46,7 @@
> #include "expop.h"
> #include "arch/amd64-linux-tdesc.h"
> #include "inferior.h"
> +#include "x86-tdep.h"
>
> /* The syscall's XML filename for i386. */
> #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
> @@ -1592,6 +1593,14 @@ amd64_linux_record_signal (struct gdbarch *gdbarch,
> return 0;
> }
>
> +/* Get shadow stack pointer state from core dump. */
> +
> +static bool
> +amd64_linux_core_read_ssp_state_p (bfd *abfd)
> +{
> + return bfd_get_section_by_name (abfd, ".reg-ssp") != NULL;
We're using nullptr nowadays.
> +}
> +
> /* Get Linux/x86 target description from core dump. */
>
> static const struct target_desc *
> @@ -1601,11 +1610,14 @@ amd64_linux_core_read_description (struct gdbarch *gdbarch,
> {
> /* Linux/x86-64. */
> x86_xsave_layout layout;
> - uint64_t xcr0 = i386_linux_core_read_xsave_info (abfd, layout);
> - if (xcr0 == 0)
> - xcr0 = X86_XSTATE_SSE_MASK;
> + uint64_t xstate_bv_mask = i386_linux_core_read_xsave_info (abfd, layout);
> + if (xstate_bv_mask == 0)
> + xstate_bv_mask = X86_XSTATE_SSE_MASK;
> +
> + if (amd64_linux_core_read_ssp_state_p (abfd))
> + xstate_bv_mask |= X86_XSTATE_CET_U;
>
> - return amd64_linux_read_description (xcr0 & X86_XSTATE_ALL_MASK,
> + return amd64_linux_read_description (xstate_bv_mask & X86_XSTATE_ALL_MASK,
> gdbarch_ptr_bit (gdbarch) == 32);
> }
>
> @@ -1636,6 +1648,35 @@ static const struct regset amd64_linux_xstateregset =
> amd64_linux_collect_xstateregset
> };
>
> +/* Supply shadow stack pointer register from the buffer SSP to the
> + register cache REGCACHE. */
s/from buffer SSP/from SSP? Otherwise it seems to read strangely.
> +
> +static void
> +amd64_linux_supply_ssp (const regset *regset,
> + regcache *regcache, int regnum,
> + const void *ssp, size_t len)
> +{
> + x86_supply_ssp (regcache, *static_cast<const uint64_t *> (ssp));
> +}
> +
> +/* Collect the shadow stack pointer register from the register cache
> + REGCACHE and store it in SSP. */
> +
> +static void
> +amd64_linux_collect_ssp (const regset *regset,
> + const regcache *regcache, int regnum,
> + void *ssp, size_t len)
> +{
> + x86_collect_ssp (regcache, *static_cast<uint64_t *> (ssp));
> +}
> +
> +/* Shadow stack pointer register. */
> +
> +static const struct regset amd64_linux_ssp_register
> + {
> + NULL, amd64_linux_supply_ssp, amd64_linux_collect_ssp
> + };
> +
> /* Iterate over core file register note sections. */
>
> static void
> @@ -1652,6 +1693,14 @@ amd64_linux_iterate_over_regset_sections (struct gdbarch *gdbarch,
> cb (".reg-xstate", tdep->xsave_layout.sizeof_xsave,
> tdep->xsave_layout.sizeof_xsave, &amd64_linux_xstateregset,
> "XSAVE extended state", cb_data);
> +
> + /* SSP can be unavailable. Thus, we need to check the register status
> + in case we write a core file (regcache != nullptr). */
> + if (tdep->ssp_regnum > 0
> + && (regcache == nullptr
> + || REG_VALID == regcache->get_register_status (tdep->ssp_regnum)))
> + cb (".reg-ssp", 8, 8, &amd64_linux_ssp_register,
> + "shadow stack pointer", cb_data);
> }
>
> /* The instruction sequences used in x86_64 machines for a
> diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
> new file mode 100644
> index 00000000000..25cc1529f0d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
> @@ -0,0 +1,50 @@
> +# Copyright 2021-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 the shadow stack pointer note in core dumps.
> +
> +require allow_ssp_tests
> +
> +standard_testfile amd64-shadow-stack.c
> +set gcorefile ${binfile}.gcore
> +
> +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 -1
> + }
> +
> + if { ![runto_main] } {
> + return -1
> + }
> +
> + # Save ssp for comparison in the corefile session.
> + set ssp [get_hexadecimal_valueof "\$pl3_ssp" ""]
> +
> + if { ![gdb_gcore_cmd $gcorefile "save a corefile"] } {
> + return -1
> + }
> +
> + # Now restart gdb and load the corefile.
> + clean_restart ${binfile}
> +
> + gdb_test "core ${gcorefile}" \
> + "Core was generated by .*" "re-load generated corefile"
> +
> + gdb_test "print /x \$pl3_ssp" "= $ssp"
> +}
The above test seems to exercise the case where a core file is generated by the gcore command.
Do we also need to exercise the case where the linux kernel dumps the core file? Like Thiago's
AArch64 series for GCS?
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 06/11] gdb: amd64 linux coredump support with shadow stack.
2025-06-19 9:24 ` Luis Machado
@ 2025-06-23 13:16 ` Schimpe, Christina
0 siblings, 0 replies; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-23 13:16 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
Hi Luis,
> -----Original Message-----
> From: Luis Machado <luis.machado@arm.com>
> Sent: Thursday, June 19, 2025 11:25 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> Subject: Re: [PATCH v4 06/11] gdb: amd64 linux coredump support with shadow
> stack.
>
> On 6/17/25 13:11, Christina Schimpe wrote:
> > From: Felix Willgerodt <felix.willgerodt@intel.com>
> >
> > Intel's Control-Flow Enforcement Technology (CET) provides the shadow
> > stack feature for the x86 architecture.
> >
> > This commit adds support to write and read the shadow-stack node in
> > corefiles. This helps debugging return address violations post-mortem.
> > The format is synced with the linux kernel commit "x86: Add PTRACE
> > interface for shadow stack". As the linux kernel restricts shadow
> > stack support to 64-bit, apply the fix for amd64 only.
> >
> > Co-Authored-By: Christina Schimpe <christina.schimpe@intel.com>
> >
> > Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> > ---
> > gdb/amd64-linux-tdep.c | 57 +++++++++++++++++--
> > .../gdb.arch/amd64-shadow-stack-corefile.exp | 50 ++++++++++++++++
> > 2 files changed, 103 insertions(+), 4 deletions(-) create mode
> > 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
> >
> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> > f23db4dce22..d806d3cb1f7 100644
> > --- a/gdb/amd64-linux-tdep.c
> > +++ b/gdb/amd64-linux-tdep.c
> > @@ -46,6 +46,7 @@
> > #include "expop.h"
> > #include "arch/amd64-linux-tdesc.h"
> > #include "inferior.h"
> > +#include "x86-tdep.h"
> >
> > /* The syscall's XML filename for i386. */ #define
> > XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
> > @@ -1592,6 +1593,14 @@ amd64_linux_record_signal (struct gdbarch
> *gdbarch,
> > return 0;
> > }
> >
> > +/* Get shadow stack pointer state from core dump. */
> > +
> > +static bool
> > +amd64_linux_core_read_ssp_state_p (bfd *abfd) {
> > + return bfd_get_section_by_name (abfd, ".reg-ssp") != NULL;
>
> We're using nullptr nowadays.
Will fix.
>
> > +}
> > +
> > /* Get Linux/x86 target description from core dump. */
> >
> > static const struct target_desc *
> > @@ -1601,11 +1610,14 @@ amd64_linux_core_read_description (struct
> > gdbarch *gdbarch, {
> > /* Linux/x86-64. */
> > x86_xsave_layout layout;
> > - uint64_t xcr0 = i386_linux_core_read_xsave_info (abfd, layout);
> > - if (xcr0 == 0)
> > - xcr0 = X86_XSTATE_SSE_MASK;
> > + uint64_t xstate_bv_mask = i386_linux_core_read_xsave_info (abfd,
> > + layout); if (xstate_bv_mask == 0)
> > + xstate_bv_mask = X86_XSTATE_SSE_MASK;
> > +
> > + if (amd64_linux_core_read_ssp_state_p (abfd))
> > + xstate_bv_mask |= X86_XSTATE_CET_U;
> >
> > - return amd64_linux_read_description (xcr0 & X86_XSTATE_ALL_MASK,
> > + return amd64_linux_read_description (xstate_bv_mask &
> > + X86_XSTATE_ALL_MASK,
> > gdbarch_ptr_bit (gdbarch) == 32); }
> >
> > @@ -1636,6 +1648,35 @@ static const struct regset
> amd64_linux_xstateregset =
> > amd64_linux_collect_xstateregset
> > };
> >
> > +/* Supply shadow stack pointer register from the buffer SSP to the
> > + register cache REGCACHE. */
>
> s/from buffer SSP/from SSP? Otherwise it seems to read strangely.
Will fix.
> > +
> > +static void
> > +amd64_linux_supply_ssp (const regset *regset,
> > + regcache *regcache, int regnum,
> > + const void *ssp, size_t len)
> > +{
> > + x86_supply_ssp (regcache, *static_cast<const uint64_t *> (ssp)); }
> > +
> > +/* Collect the shadow stack pointer register from the register cache
> > + REGCACHE and store it in SSP. */
> > +
> > +static void
> > +amd64_linux_collect_ssp (const regset *regset,
> > + const regcache *regcache, int regnum,
> > + void *ssp, size_t len)
> > +{
> > + x86_collect_ssp (regcache, *static_cast<uint64_t *> (ssp)); }
> > +
> > +/* Shadow stack pointer register. */
> > +
> > +static const struct regset amd64_linux_ssp_register
> > + {
> > + NULL, amd64_linux_supply_ssp, amd64_linux_collect_ssp
> > + };
> > +
> > /* Iterate over core file register note sections. */
> >
> > static void
> > @@ -1652,6 +1693,14 @@ amd64_linux_iterate_over_regset_sections (struct
> gdbarch *gdbarch,
> > cb (".reg-xstate", tdep->xsave_layout.sizeof_xsave,
> > tdep->xsave_layout.sizeof_xsave, &amd64_linux_xstateregset,
> > "XSAVE extended state", cb_data);
> > +
> > + /* SSP can be unavailable. Thus, we need to check the register status
> > + in case we write a core file (regcache != nullptr). */
> > + if (tdep->ssp_regnum > 0
> > + && (regcache == nullptr
> > + || REG_VALID == regcache->get_register_status (tdep->ssp_regnum)))
> > + cb (".reg-ssp", 8, 8, &amd64_linux_ssp_register,
> > + "shadow stack pointer", cb_data);
> > }
> >
> > /* The instruction sequences used in x86_64 machines for a diff --git
> > a/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
> > b/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
> > new file mode 100644
> > index 00000000000..25cc1529f0d
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-corefile.exp
> > @@ -0,0 +1,50 @@
> > +# Copyright 2021-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 the shadow stack pointer note in core dumps.
> > +
> > +require allow_ssp_tests
> > +
> > +standard_testfile amd64-shadow-stack.c set gcorefile ${binfile}.gcore
> > +
> > +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 -1
> > + }
> > +
> > + if { ![runto_main] } {
> > + return -1
> > + }
> > +
> > + # Save ssp for comparison in the corefile session.
> > + set ssp [get_hexadecimal_valueof "\$pl3_ssp" ""]
> > +
> > + if { ![gdb_gcore_cmd $gcorefile "save a corefile"] } {
> > + return -1
> > + }
> > +
> > + # Now restart gdb and load the corefile.
> > + clean_restart ${binfile}
> > +
> > + gdb_test "core ${gcorefile}" \
> > + "Core was generated by .*" "re-load generated corefile"
> > +
> > + gdb_test "print /x \$pl3_ssp" "= $ssp"
> > +}
>
> The above test seems to exercise the case where a core file is generated by the
> gcore command.
>
> Do we also need to exercise the case where the linux kernel dumps the core
> file? Like Thiago's
> AArch64 series for GCS?
Hm, good question - I believe it's better to test this actually.
If I don't hear other feedback against it, I'll extend the test for v5.
Thanks,
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
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
` (5 preceding siblings ...)
2025-06-17 12:11 ` [PATCH v4 06/11] gdb: amd64 linux coredump support with shadow stack Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-19 9:25 ` Luis Machado
2025-06-20 1:52 ` Thiago Jung Bauermann
2025-06-17 12:11 ` [PATCH v4 08/11] gdb, gdbarch: Enable inferior calls for shadow stack support Christina Schimpe
` (3 subsequent siblings)
10 siblings, 2 replies; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
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>
---
gdb/amd64-linux-tdep.c | 84 ++++++++++++++++++
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, 239 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 d806d3cb1f7..9436f0b190c 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -47,6 +47,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"
@@ -1917,6 +1919,87 @@ 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);
+
+ /* 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. */
+ CORE_ADDR new_ssp
+ = ssp + amd64_linux_shadow_stack_element_size_aligned (gdbarch);
+
+ /* 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)
@@ -1974,6 +2057,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 1e339b59e2e..c532b8758bf 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -47,6 +47,7 @@
#include "gdbsupport/unordered_map.h"
#include <ctype.h>
+#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;
}
}
@@ -3036,6 +3043,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 7485fc132a6..7083635b976 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;
}
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-17 12:11 ` [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux Christina Schimpe
@ 2025-06-19 9:25 ` Luis Machado
2025-06-20 1:42 ` Thiago Jung Bauermann
2025-06-23 15:00 ` Schimpe, Christina
2025-06-20 1:52 ` Thiago Jung Bauermann
1 sibling, 2 replies; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:25 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe 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.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
> gdb/amd64-linux-tdep.c | 84 ++++++++++++++++++
> 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, 239 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 d806d3cb1f7..9436f0b190c 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -47,6 +47,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"
> @@ -1917,6 +1919,87 @@ 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);
> +
> + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
Same typo as before, period after "v6.6".
> + 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);
> +
> + /* If NEW_SSP points to the end of or before (<=) the current
> + shadow stack memory range we consider NEW_SSP as valid (but
> + empty). */
I couldn't quite understand the difference between the empty case and the
unavailable case. But maybe I just don't fully understand the feature.
Would it be possible to make the comment a bit more clear?
> + 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)
> @@ -1974,6 +2057,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
<...>
Otherwise the rest looks OK to me.
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-19 9:25 ` Luis Machado
@ 2025-06-20 1:42 ` Thiago Jung Bauermann
2025-06-23 14:55 ` Schimpe, Christina
2025-06-23 15:00 ` Schimpe, Christina
1 sibling, 1 reply; 53+ messages in thread
From: Thiago Jung Bauermann @ 2025-06-20 1:42 UTC (permalink / raw)
To: Luis Machado; +Cc: Christina Schimpe, gdb-patches, eliz
Luis Machado <luis.machado@arm.com> writes:
> On 6/17/25 13:11, Christina Schimpe wrote:
>> + 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);
>> +
>> + /* If NEW_SSP points to the end of or before (<=) the current
>> + shadow stack memory range we consider NEW_SSP as valid (but
>> + empty). */
>
> I couldn't quite understand the difference between the empty case and the
> unavailable case. But maybe I just don't fully understand the feature.
>
> Would it be possible to make the comment a bit more clear?
I understood it to mean that if new_ssp == range.second, then it points
to the top of the stack and there aren't any elements.
Whereas if new_ssp points outside of the shadow stack area, then it's
garbage and we failed to unwind it, hence the unavailable value.
Christina, please correct me if I'm wrong.
But now looking at this again, I think there's an off-by-one error:
range.second is the first address outside of the memory range, so the
comparison needs to be strictly less than. And the shadow stack will be
empty if new_ssp == range.second - 1 (there's no need to check for that,
though).
>> + 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;
>> +}
--
Thiago
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-20 1:42 ` Thiago Jung Bauermann
@ 2025-06-23 14:55 ` Schimpe, Christina
2025-06-23 23:26 ` Thiago Jung Bauermann
0 siblings, 1 reply; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-23 14:55 UTC (permalink / raw)
To: Thiago Jung Bauermann, Luis Machado; +Cc: gdb-patches, eliz
> -----Original Message-----
> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Sent: Friday, June 20, 2025 3:43 AM
> To: Luis Machado <luis.machado@arm.com>
> Cc: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org; eliz@gnu.org
> Subject: Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register
> unwinding for amd64 linux.
>
> Luis Machado <luis.machado@arm.com> writes:
>
> > On 6/17/25 13:11, Christina Schimpe wrote:
> >> + 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);
> >> +
> >> + /* If NEW_SSP points to the end of or before (<=) the current
> >> + shadow stack memory range we consider NEW_SSP as valid (but
> >> + empty). */
> >
> > I couldn't quite understand the difference between the empty case and
> > the unavailable case. But maybe I just don't fully understand the feature.
> >
> > Would it be possible to make the comment a bit more clear?
>
> I understood it to mean that if new_ssp == range.second, then it points to
> the top of the stack and there aren't any elements.
>
> Whereas if new_ssp points outside of the shadow stack area, then it's
> garbage and we failed to unwind it, hence the unavailable value.
>
> Christina, please correct me if I'm wrong.
>
> But now looking at this again, I think there's an off-by-one error:
> range.second is the first address outside of the memory range, so the
> comparison needs to be strictly less than. And the shadow stack will be
> empty if new_ssp == range.second - 1 (there's no need to check for that,
> though).
>
> >> + 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;
> >> +}
Hi Thiago,
> Whereas if new_ssp points outside of the shadow stack area, then it's
> garbage and we failed to unwind it, hence the unavailable value.
Yes, this is how it should be. 😊
But I think the current implementation should be correct.
I wrote a small testprogram, which enables shadow stack manually using ARCH_PRCTL in enable_ssp:
~~~
int call ()
{
return 0;
}
int enable_ssp ()
{
int ret;
if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {
printf("[FAIL]\tEnabling shadow stack failed\n");
return 1;
}
ret = call (); // stop in enable_ssp
if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {
ret = 1;
printf("[FAIL]\tDisabling shadow stack failed\n");
}
return ret;
}
int main ()
{
return enable_ssp ();
}
~~~
If we stop in enable_ssp, ("ret = call ()"), we have the scenario that shadow stack is enabled,
but no call instruction has been executed yet.
So we'll have an empty shadow stack, but SSP is valid and pointing to the end address
(==range.second) of the shadow stack address space:
~~~
(gdb) r
Starting program: /tmp/main
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, enable_ssp () at main.c:52
52 ret = call (); // stop in enable_ssp
(gdb) p $pl3_ssp
$1 = (void *) 0x7ffff7c00000
(gdb) info proc mappings
process 94192
Mapped address spaces:
Start Addr End Addr Size Offset Perms File
[...]
0x0000555555558000 0x0000555555559000 0x1000 0x3000 rw-p /tmp/main
0x00007ffff7400000 0x00007ffff7c00000 0x800000 0x0 rw-p
[...]
~~~
Checking the unwinding we see that one frame above SSP becomes unavailable:
~~~
(gdb) up
#1 0x00005555555551d4 in main () at main.c:63
63 return enable_ssp ();
(gdb) p $pl3_ssp
$3 = <unavailable>
(gdb) down
#0 enable_ssp () at main.c:52
52 ret = call (); // stop in enable_ssp
(gdb) p $pl3_ssp
$4 = (void *) 0x7ffff7c00000
~~~
Or unwinding from call:
~~~
Breakpoint 1, call () at main.c:41
41 return 0;
(gdb) p $pl3_ssp
$1 = (void *) 0x7ffff7bffff8
(gdb) up
#1 0x000055555555518a in enable_ssp () at main.c:52
52 ret = call (); // stop in enable_ssp
(gdb) p $pl3_ssp
$2 = (void *) 0x7ffff7c00000
(gdb) up
#2 0x00005555555551d4 in main () at main.c:63
63 return enable_ssp ();
(gdb) p $pl3_ssp
$3 = <unavailable>
(gdb) down
#1 0x000055555555518a in enable_ssp () at main.c:52
52 ret = call (); // stop in enable_ssp
(gdb) p $pl3_ssp
$4 = (void *) 0x7ffff7c00000
(gdb) down
#0 call () at main.c:41
41 return 0;
(gdb) p $pl3_ssp
$5 = (void *) 0x7ffff7bffff8
~~~
So the check
"if (new_ssp <= range.second)"
seems correct to me.
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-23 14:55 ` Schimpe, Christina
@ 2025-06-23 23:26 ` Thiago Jung Bauermann
0 siblings, 0 replies; 53+ messages in thread
From: Thiago Jung Bauermann @ 2025-06-23 23:26 UTC (permalink / raw)
To: Schimpe, Christina; +Cc: Luis Machado, gdb-patches, eliz
Hello Christina,
"Schimpe, Christina" <christina.schimpe@intel.com> writes:
>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Friday, June 20, 2025 3:43 AM
>> To: Luis Machado <luis.machado@arm.com>
>> Cc: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>> patches@sourceware.org; eliz@gnu.org
>> Subject: Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register
>> unwinding for amd64 linux.
>>
>> Luis Machado <luis.machado@arm.com> writes:
>>
>> > On 6/17/25 13:11, Christina Schimpe wrote:
>> >> + 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);
>> >> +
>> >> + /* If NEW_SSP points to the end of or before (<=) the current
>> >> + shadow stack memory range we consider NEW_SSP as valid (but
>> >> + empty). */
>> >
>> > I couldn't quite understand the difference between the empty case and
>> > the unavailable case. But maybe I just don't fully understand the feature.
>> >
>> > Would it be possible to make the comment a bit more clear?
>>
>> I understood it to mean that if new_ssp == range.second, then it points to
>> the top of the stack and there aren't any elements.
>>
>> Whereas if new_ssp points outside of the shadow stack area, then it's
>> garbage and we failed to unwind it, hence the unavailable value.
>>
>> Christina, please correct me if I'm wrong.
>>
>> But now looking at this again, I think there's an off-by-one error:
>> range.second is the first address outside of the memory range, so the
>> comparison needs to be strictly less than. And the shadow stack will be
>> empty if new_ssp == range.second - 1 (there's no need to check for that,
>> though).
>>
>> >> + 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;
>> >> +}
>
> Hi Thiago,
>
>> Whereas if new_ssp points outside of the shadow stack area, then it's
>> garbage and we failed to unwind it, hence the unavailable value.
>
> Yes, this is how it should be. 😊
>
> But I think the current implementation should be correct.
> I wrote a small testprogram, which enables shadow stack manually using ARCH_PRCTL in enable_ssp:
>
> ~~~
>
> int call ()
> {
> return 0;
> }
>
> int enable_ssp ()
> {
> int ret;
> if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {
> printf("[FAIL]\tEnabling shadow stack failed\n");
> return 1;
> }
>
> ret = call (); // stop in enable_ssp
>
> if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {
> ret = 1;
> printf("[FAIL]\tDisabling shadow stack failed\n");
> }
> return ret;
> }
>
> int main ()
> {
> return enable_ssp ();
> }
>
> ~~~
>
> If we stop in enable_ssp, ("ret = call ()"), we have the scenario that shadow stack is enabled,
> but no call instruction has been executed yet.
> So we'll have an empty shadow stack, but SSP is valid and pointing to the end address
> (==range.second) of the shadow stack address space:
Thanks for the program. I see that Intel and AArch64 behave differently
in this respect. I believe that this is because on AArch64, a new stack
contains an initial value of 0 at the top. From the Linux kernel
documentation¹:
When a stack is allocated by enabling GCS or during thread creation
then the top 8 bytes of the stack will be initialised to 0 and
GCSPR_EL0 will be set to point to the address of this 0 value, this
can be used to detect the top of the stack.
> ~~~
> (gdb) r
> Starting program: /tmp/main
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
>
> Breakpoint 1, enable_ssp () at main.c:52
> 52 ret = call (); // stop in enable_ssp
> (gdb) p $pl3_ssp
> $1 = (void *) 0x7ffff7c00000
> (gdb) info proc mappings
> process 94192
> Mapped address spaces:
>
> Start Addr End Addr Size Offset Perms File
> [...]
> 0x0000555555558000 0x0000555555559000 0x1000 0x3000 rw-p /tmp/main
> 0x00007ffff7400000 0x00007ffff7c00000 0x800000 0x0 rw-p
> [...]
On AArch64 the GCSPR is within the GCS memory range:
(gdb) r
Starting program: /home/bauermann/tmp/test-gcs
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
Breakpoint 1, enable_gcspr () at test-gcs.c:63
⚠ warning: Source file is more recent than executable.
63 ret = call (); // stop in enable_ssp
(gdb) p $gcspr
$1 = (void *) 0xfffff7bffff8
(gdb) info proc mappings
process 3228
Mapped address spaces:
Start Addr End Addr Size Offset Perms File
⋮
0x0000fffff7800000 0x0000fffff7c00000 0x400000 0x0 rw-p
> ~~~
>
> Checking the unwinding we see that one frame above SSP becomes unavailable:
> ~~~
> (gdb) up
> #1 0x00005555555551d4 in main () at main.c:63
> 63 return enable_ssp ();
> (gdb) p $pl3_ssp
> $3 = <unavailable>
> (gdb) down
> #0 enable_ssp () at main.c:52
> 52 ret = call (); // stop in enable_ssp
> (gdb) p $pl3_ssp
> $4 = (void *) 0x7ffff7c00000
What I get on AArch64. The last command shows the zeroed entry at the
top of the stack.
(gdb) up
#1 0x0000aaaaaaaa0900 in main () at test-gcs.c:77
77 return enable_gcspr ();
(gdb) p $gcspr
$2 = <unavailable>
(gdb) down
#0 enable_gcspr () at test-gcs.c:63
63 ret = call (); // stop in enable_ssp
(gdb) p $gcspr
$3 = (void *) 0xfffff7bffff8
(gdb) x/xg $gcspr
0xfffff7bffff8: 0x0000000000000000
> ~~~
>
> Or unwinding from call:
> ~~~
> Breakpoint 1, call () at main.c:41
> 41 return 0;
> (gdb) p $pl3_ssp
> $1 = (void *) 0x7ffff7bffff8
> (gdb) up
> #1 0x000055555555518a in enable_ssp () at main.c:52
> 52 ret = call (); // stop in enable_ssp
> (gdb) p $pl3_ssp
> $2 = (void *) 0x7ffff7c00000
> (gdb) up
> #2 0x00005555555551d4 in main () at main.c:63
> 63 return enable_ssp ();
> (gdb) p $pl3_ssp
> $3 = <unavailable>
> (gdb) down
> #1 0x000055555555518a in enable_ssp () at main.c:52
> 52 ret = call (); // stop in enable_ssp
> (gdb) p $pl3_ssp
> $4 = (void *) 0x7ffff7c00000
> (gdb) down
> #0 call () at main.c:41
> 41 return 0;
> (gdb) p $pl3_ssp
> $5 = (void *) 0x7ffff7bffff8
In this example the GCSPR is also always within the GCS range. And you
can see the zeroed entry at the top as well:
Breakpoint 2, call () at test-gcs.c:50
50 return 0;
(gdb) p $gcspr
$4 = (void *) 0xfffff7bffff0
(gdb) x/xg $gcspr
0xfffff7bffff0: 0x0000aaaaaaaa08a4
(gdb) x/2xg $gcspr
0xfffff7bffff0: 0x0000aaaaaaaa08a4 0x0000000000000000
(gdb) up
#1 0x0000aaaaaaaa08a4 in enable_gcspr () at test-gcs.c:63
63 ret = call (); // stop in enable_ssp
(gdb) p $gcspr
$5 = (void *) 0xfffff7bffff8
(gdb) x/xg $gcspr
0xfffff7bffff8: 0x0000000000000000
(gdb) up
#2 0x0000aaaaaaaa0900 in main () at test-gcs.c:77
77 return enable_gcspr ();
(gdb) p $gcspr
$6 = <unavailable>
(gdb) down
#1 0x0000aaaaaaaa08a4 in enable_gcspr () at test-gcs.c:63
63 ret = call (); // stop in enable_ssp
(gdb) p $gcspr
$7 = (void *) 0xfffff7bffff8
(gdb) down
#0 call () at test-gcs.c:50
50 return 0;
(gdb) p $gcspr
$8 = (void *) 0xfffff7bffff0
> ~~~
>
> So the check
>
> "if (new_ssp <= range.second)"
>
> seems correct to me.
This is a subtle difference between our systems. Thanks for the example
and the explanation. It was very helpful.
--
Thiago
¹ https://www.kernel.org/doc/html/latest/arch/arm64/gcs.html#allocation-of-guarded-control-stacks
^ permalink raw reply [flat|nested] 53+ messages in thread
* RE: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-19 9:25 ` Luis Machado
2025-06-20 1:42 ` Thiago Jung Bauermann
@ 2025-06-23 15:00 ` Schimpe, Christina
2025-06-23 15:06 ` Luis Machado
1 sibling, 1 reply; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-23 15:00 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
Hi Luis,
Thanks for the feedback. Please find my comments below.
> -----Original Message-----
> From: Luis Machado <luis.machado@arm.com>
> Sent: Thursday, June 19, 2025 11:25 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> Subject: Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register
> unwinding for amd64 linux.
>
> On 6/17/25 13:11, Christina Schimpe 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.
> >
> > Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> > ---
> > gdb/amd64-linux-tdep.c | 84 ++++++++++++++++++
> > 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, 239 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
> > d806d3cb1f7..9436f0b190c 100644
> > --- a/gdb/amd64-linux-tdep.c
> > +++ b/gdb/amd64-linux-tdep.c
> > @@ -47,6 +47,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"
> > @@ -1917,6 +1919,87 @@ 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);
> > +
> > + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
>
> Same typo as before, period after "v6.6".
Will fix.
>
> > + 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);
> > +
> > + /* If NEW_SSP points to the end of or before (<=) the current
> > + shadow stack memory range we consider NEW_SSP as valid (but
> > + empty). */
>
> I couldn't quite understand the difference between the empty case and the
> unavailable case. But maybe I just don't fully understand the feature.
>
> Would it be possible to make the comment a bit more clear?
Is this a bit clearer?
"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). "
Please also see my answer to Thiago:
https://sourceware.org/pipermail/gdb-patches/2025-June/218908.html
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-23 15:00 ` Schimpe, Christina
@ 2025-06-23 15:06 ` Luis Machado
2025-06-23 23:36 ` Thiago Jung Bauermann
0 siblings, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-23 15:06 UTC (permalink / raw)
To: Schimpe, Christina, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/23/25 16:00, Schimpe, Christina wrote:
> Hi Luis,
>
> Thanks for the feedback. Please find my comments below.
>
>> -----Original Message-----
>> From: Luis Machado <luis.machado@arm.com>
>> Sent: Thursday, June 19, 2025 11:25 AM
>> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>> patches@sourceware.org
>> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
>> Subject: Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register
>> unwinding for amd64 linux.
>>
>> On 6/17/25 13:11, Christina Schimpe 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.
>>>
>>> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>>> ---
>>> gdb/amd64-linux-tdep.c | 84 ++++++++++++++++++
>>> 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, 239 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
>>> d806d3cb1f7..9436f0b190c 100644
>>> --- a/gdb/amd64-linux-tdep.c
>>> +++ b/gdb/amd64-linux-tdep.c
>>> @@ -47,6 +47,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"
>>> @@ -1917,6 +1919,87 @@ 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);
>>> +
>>> + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
>>
>> Same typo as before, period after "v6.6".
>
> Will fix.
>
>>
>>> + 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);
>>> +
>>> + /* If NEW_SSP points to the end of or before (<=) the current
>>> + shadow stack memory range we consider NEW_SSP as valid (but
>>> + empty). */
>>
>> I couldn't quite understand the difference between the empty case and the
>> unavailable case. But maybe I just don't fully understand the feature.
>>
>> Would it be possible to make the comment a bit more clear?
>
> Is this a bit clearer?
>
> "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). "
Yes, that clear it up. Thanks!
>
> Please also see my answer to Thiago:
> https://sourceware.org/pipermail/gdb-patches/2025-June/218908.html
Thanks. It would be nice if both series were consistent in this regard (<= or <).
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-23 15:06 ` Luis Machado
@ 2025-06-23 23:36 ` Thiago Jung Bauermann
0 siblings, 0 replies; 53+ messages in thread
From: Thiago Jung Bauermann @ 2025-06-23 23:36 UTC (permalink / raw)
To: Luis Machado; +Cc: Schimpe, Christina, gdb-patches, eliz
Hello Luis,
Luis Machado <luis.machado@arm.com> writes:
> On 6/23/25 16:00, Schimpe, Christina wrote:
>> Hi Luis,
>>
>> Thanks for the feedback. Please find my comments below.
>>
>>> -----Original Message-----
>>> From: Luis Machado <luis.machado@arm.com>
>>> Sent: Thursday, June 19, 2025 11:25 AM
>>> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>>> patches@sourceware.org
>>> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
>>> Subject: Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register
>>> unwinding for amd64 linux.
>>>
>>> On 6/17/25 13:11, Christina Schimpe wrote:
>>>> + 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);
>>>> +
>>>> + /* If NEW_SSP points to the end of or before (<=) the current
>>>> + shadow stack memory range we consider NEW_SSP as valid (but
>>>> + empty). */
>>>
>>> I couldn't quite understand the difference between the empty case and the
>>> unavailable case. But maybe I just don't fully understand the feature.
>>>
>>> Would it be possible to make the comment a bit more clear?
>>
>> Is this a bit clearer?
>>
>> "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). "
>
> Yes, that clear it up. Thanks!
>
>>
>> Please also see my answer to Thiago:
>> https://sourceware.org/pipermail/gdb-patches/2025-June/218908.html
>
> Thanks. It would be nice if both series were consistent in this regard (<= or <).
I just realised that AArch64 and Intel represent an empty stack
differently, so it's correct to have <= on Intel and < on AArch64:
https://inbox.sourceware.org/gdb-patches/877c12vy3h.fsf@linaro.org/
--
Thiago
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
2025-06-17 12:11 ` [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux Christina Schimpe
2025-06-19 9:25 ` Luis Machado
@ 2025-06-20 1:52 ` Thiago Jung Bauermann
1 sibling, 0 replies; 53+ messages in thread
From: Thiago Jung Bauermann @ 2025-06-20 1:52 UTC (permalink / raw)
To: Christina Schimpe; +Cc: gdb-patches, eliz
Hello Christina,
I have one late, small suggestion on this patch. I wouldn't have
mentioned it but if there's going to be a v5 for some other reason then
it might be worth making the change, if you agree with it:
Christina Schimpe <christina.schimpe@intel.com> writes:
> @@ -3036,6 +3043,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)
All callers pass a range argument, and this function doesn't even check
if the pointer is nullptr before dereferencing it. For such cases, using
a reference rather than a pointer is better, since the compiler will
make sure that the argument is never nullptr.
> +{
> + 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;
> +}
--
Thiago
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 08/11] gdb, gdbarch: Enable inferior calls for shadow stack support.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
` (6 preceding siblings ...)
2025-06-17 12:11 ` [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-19 9:25 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls Christina Schimpe
` (2 subsequent siblings)
10 siblings, 1 reply; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
Inferior calls in GDB reset the current PC to the beginning of the function
that is called. As no call instruction is executed the new return address
needs to be pushed to the shadow stack and the shadow stack pointer needs
to be updated.
This commit adds a new gdbarch method to push an address on the shadow
stack. The method is used to adapt the function 'call_function_by_hand_dummy'
for inferior call shadow stack support.
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
gdb/gdbarch-gen.c | 32 ++++++++++++++++++++++++++++++++
gdb/gdbarch-gen.h | 14 ++++++++++++++
gdb/gdbarch_components.py | 16 ++++++++++++++++
gdb/infcall.c | 14 ++++++++++----
4 files changed, 72 insertions(+), 4 deletions(-)
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index 32d16598940..3ca19c427a3 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -262,6 +262,7 @@ struct gdbarch
gdbarch_read_core_file_mappings_ftype *read_core_file_mappings = default_read_core_file_mappings;
gdbarch_use_target_description_from_corefile_notes_ftype *use_target_description_from_corefile_notes = default_use_target_description_from_corefile_notes;
gdbarch_core_parse_exec_context_ftype *core_parse_exec_context = default_core_parse_exec_context;
+ gdbarch_shadow_stack_push_ftype *shadow_stack_push = nullptr;
};
/* Create a new ``struct gdbarch'' based on information provided by
@@ -535,6 +536,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
/* Skip verify of read_core_file_mappings, invalid_p == 0. */
/* Skip verify of use_target_description_from_corefile_notes, invalid_p == 0. */
/* Skip verify of core_parse_exec_context, invalid_p == 0. */
+ /* Skip verify of shadow_stack_push, has predicate. */
if (!log.empty ())
internal_error (_("verify_gdbarch: the following are invalid ...%s"),
log.c_str ());
@@ -1406,6 +1408,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
gdb_printf (file,
"gdbarch_dump: core_parse_exec_context = <%s>\n",
host_address_to_string (gdbarch->core_parse_exec_context));
+ gdb_printf (file,
+ "gdbarch_dump: gdbarch_shadow_stack_push_p() = %d\n",
+ gdbarch_shadow_stack_push_p (gdbarch));
+ gdb_printf (file,
+ "gdbarch_dump: shadow_stack_push = <%s>\n",
+ host_address_to_string (gdbarch->shadow_stack_push));
if (gdbarch->dump_tdep != NULL)
gdbarch->dump_tdep (gdbarch, file);
}
@@ -5551,3 +5559,27 @@ set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch,
{
gdbarch->core_parse_exec_context = core_parse_exec_context;
}
+
+bool
+gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch)
+{
+ gdb_assert (gdbarch != NULL);
+ return gdbarch->shadow_stack_push != NULL;
+}
+
+void
+gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache)
+{
+ gdb_assert (gdbarch != NULL);
+ gdb_assert (gdbarch->shadow_stack_push != NULL);
+ if (gdbarch_debug >= 2)
+ gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_push called\n");
+ gdbarch->shadow_stack_push (gdbarch, new_addr, regcache);
+}
+
+void
+set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
+ gdbarch_shadow_stack_push_ftype shadow_stack_push)
+{
+ gdbarch->shadow_stack_push = shadow_stack_push;
+}
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index 313a8f198fd..c276cd66d71 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1801,3 +1801,17 @@ extern void set_gdbarch_use_target_description_from_corefile_notes (struct gdbar
typedef core_file_exec_context (gdbarch_core_parse_exec_context_ftype) (struct gdbarch *gdbarch, bfd *cbfd);
extern core_file_exec_context gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, bfd *cbfd);
extern void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarch_core_parse_exec_context_ftype *core_parse_exec_context);
+
+/* Some targets support special hardware-assisted control-flow protection
+ technologies. For example, the Intel Control-Flow Enforcement Technology
+ (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
+ To enable inferior calls the function shadow_stack_push has to be provided.
+
+ Push the address NEW_ADDR on the shadow stack and update the shadow stack
+ pointer. */
+
+extern bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch);
+
+typedef void (gdbarch_shadow_stack_push_ftype) (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
+extern void gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
+extern void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shadow_stack_push_ftype *shadow_stack_push);
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index ec09d955088..ab685b14ec7 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2848,3 +2848,19 @@ which all assume current_inferior() is the one to read from.
predefault="default_core_parse_exec_context",
invalid=False,
)
+
+Method(
+ comment="""
+Some targets support special hardware-assisted control-flow protection
+technologies. For example, the Intel Control-Flow Enforcement Technology
+(Intel CET) on x86 provides a shadow stack and indirect branch tracking.
+To enable inferior calls the function shadow_stack_push has to be provided.
+
+Push the address NEW_ADDR on the shadow stack and update the shadow stack
+pointer.
+""",
+ type="void",
+ name="shadow_stack_push",
+ params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")],
+ predicate=True,
+)
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 098072dfd2a..a1c80c81238 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -1448,10 +1448,16 @@ call_function_by_hand_dummy (struct value *function,
/* Create the dummy stack frame. Pass in the call dummy address as,
presumably, the ABI code knows where, in the call dummy, the
return address should be pointed. */
- sp = gdbarch_push_dummy_call (gdbarch, function,
- get_thread_regcache (inferior_thread ()),
- bp_addr, args.size (), args.data (),
- sp, return_method, struct_addr);
+ regcache *regcache = get_thread_regcache (inferior_thread ());
+ sp = gdbarch_push_dummy_call (gdbarch, function, regcache, bp_addr,
+ args.size (), args.data (), sp,
+ return_method, struct_addr);
+
+ /* Push the return address of the inferior (bp_addr) on the shadow stack
+ and update the shadow stack pointer. As we don't execute a call
+ instruction to start the inferior we need to handle this manually. */
+ if (gdbarch_shadow_stack_push_p (gdbarch))
+ gdbarch_shadow_stack_push (gdbarch, bp_addr, regcache);
/* Set up a frame ID for the dummy frame so we can pass it to
set_momentary_breakpoint. We need to give the breakpoint a frame
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 08/11] gdb, gdbarch: Enable inferior calls for shadow stack support.
2025-06-17 12:11 ` [PATCH v4 08/11] gdb, gdbarch: Enable inferior calls for shadow stack support Christina Schimpe
@ 2025-06-19 9:25 ` Luis Machado
2025-06-23 17:49 ` Schimpe, Christina
0 siblings, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:25 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> Inferior calls in GDB reset the current PC to the beginning of the function
> that is called. As no call instruction is executed the new return address
> needs to be pushed to the shadow stack and the shadow stack pointer needs
> to be updated.
>
> This commit adds a new gdbarch method to push an address on the shadow
> stack. The method is used to adapt the function 'call_function_by_hand_dummy'
> for inferior call shadow stack support.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
> gdb/gdbarch-gen.c | 32 ++++++++++++++++++++++++++++++++
> gdb/gdbarch-gen.h | 14 ++++++++++++++
> gdb/gdbarch_components.py | 16 ++++++++++++++++
> gdb/infcall.c | 14 ++++++++++----
> 4 files changed, 72 insertions(+), 4 deletions(-)
>
> diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
> index 32d16598940..3ca19c427a3 100644
> --- a/gdb/gdbarch-gen.c
> +++ b/gdb/gdbarch-gen.c
> @@ -262,6 +262,7 @@ struct gdbarch
> gdbarch_read_core_file_mappings_ftype *read_core_file_mappings = default_read_core_file_mappings;
> gdbarch_use_target_description_from_corefile_notes_ftype *use_target_description_from_corefile_notes = default_use_target_description_from_corefile_notes;
> gdbarch_core_parse_exec_context_ftype *core_parse_exec_context = default_core_parse_exec_context;
> + gdbarch_shadow_stack_push_ftype *shadow_stack_push = nullptr;
> };
>
> /* Create a new ``struct gdbarch'' based on information provided by
> @@ -535,6 +536,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
> /* Skip verify of read_core_file_mappings, invalid_p == 0. */
> /* Skip verify of use_target_description_from_corefile_notes, invalid_p == 0. */
> /* Skip verify of core_parse_exec_context, invalid_p == 0. */
> + /* Skip verify of shadow_stack_push, has predicate. */
> if (!log.empty ())
> internal_error (_("verify_gdbarch: the following are invalid ...%s"),
> log.c_str ());
> @@ -1406,6 +1408,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
> gdb_printf (file,
> "gdbarch_dump: core_parse_exec_context = <%s>\n",
> host_address_to_string (gdbarch->core_parse_exec_context));
> + gdb_printf (file,
> + "gdbarch_dump: gdbarch_shadow_stack_push_p() = %d\n",
> + gdbarch_shadow_stack_push_p (gdbarch));
> + gdb_printf (file,
> + "gdbarch_dump: shadow_stack_push = <%s>\n",
> + host_address_to_string (gdbarch->shadow_stack_push));
> if (gdbarch->dump_tdep != NULL)
> gdbarch->dump_tdep (gdbarch, file);
> }
> @@ -5551,3 +5559,27 @@ set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch,
> {
> gdbarch->core_parse_exec_context = core_parse_exec_context;
> }
> +
> +bool
> +gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch)
> +{
> + gdb_assert (gdbarch != NULL);
> + return gdbarch->shadow_stack_push != NULL;
> +}
> +
> +void
> +gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache)
> +{
> + gdb_assert (gdbarch != NULL);
> + gdb_assert (gdbarch->shadow_stack_push != NULL);
> + if (gdbarch_debug >= 2)
> + gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_push called\n");
> + gdbarch->shadow_stack_push (gdbarch, new_addr, regcache);
> +}
> +
> +void
> +set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
> + gdbarch_shadow_stack_push_ftype shadow_stack_push)
> +{
> + gdbarch->shadow_stack_push = shadow_stack_push;
> +}
> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
> index 313a8f198fd..c276cd66d71 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -1801,3 +1801,17 @@ extern void set_gdbarch_use_target_description_from_corefile_notes (struct gdbar
> typedef core_file_exec_context (gdbarch_core_parse_exec_context_ftype) (struct gdbarch *gdbarch, bfd *cbfd);
> extern core_file_exec_context gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, bfd *cbfd);
> extern void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarch_core_parse_exec_context_ftype *core_parse_exec_context);
> +
> +/* Some targets support special hardware-assisted control-flow protection
> + technologies. For example, the Intel Control-Flow Enforcement Technology
> + (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
> + To enable inferior calls the function shadow_stack_push has to be provided.
> +> + Push the address NEW_ADDR on the shadow stack and update the shadow stack
> + pointer. */
> +
> +extern bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch);
> +
> +typedef void (gdbarch_shadow_stack_push_ftype) (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
> +extern void gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
> +extern void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shadow_stack_push_ftype *shadow_stack_push);
> diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
> index ec09d955088..ab685b14ec7 100644
> --- a/gdb/gdbarch_components.py
> +++ b/gdb/gdbarch_components.py
> @@ -2848,3 +2848,19 @@ which all assume current_inferior() is the one to read from.
> predefault="default_core_parse_exec_context",
> invalid=False,
> )
> +
> +Method(
> + comment="""
> +Some targets support special hardware-assisted control-flow protection
> +technologies. For example, the Intel Control-Flow Enforcement Technology
> +(Intel CET) on x86 provides a shadow stack and indirect branch tracking.
> +To enable inferior calls the function shadow_stack_push has to be provided.
Maybe update the last part like so?
"To enable shadow stack support for inferior calls the shadow_stack_push gdbarch hook
has to be provided."
> +
> +Push the address NEW_ADDR on the shadow stack and update the shadow stack
> +pointer.
> +""",
Maybe...
"Push NEW_ADDR to the shadow stack..."?
> + type="void",
> + name="shadow_stack_push",
> + params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")],
> + predicate=True,
> +)
> diff --git a/gdb/infcall.c b/gdb/infcall.c
> index 098072dfd2a..a1c80c81238 100644
> --- a/gdb/infcall.c
> +++ b/gdb/infcall.c
> @@ -1448,10 +1448,16 @@ call_function_by_hand_dummy (struct value *function,
> /* Create the dummy stack frame. Pass in the call dummy address as,
> presumably, the ABI code knows where, in the call dummy, the
> return address should be pointed. */
> - sp = gdbarch_push_dummy_call (gdbarch, function,
> - get_thread_regcache (inferior_thread ()),
> - bp_addr, args.size (), args.data (),
> - sp, return_method, struct_addr);
> + regcache *regcache = get_thread_regcache (inferior_thread ());
> + sp = gdbarch_push_dummy_call (gdbarch, function, regcache, bp_addr,
> + args.size (), args.data (), sp,
> + return_method, struct_addr);
> +
> + /* Push the return address of the inferior (bp_addr) on the shadow stack
s/on the shadow stack/to the shadow stack
> + and update the shadow stack pointer. As we don't execute a call
> + instruction to start the inferior we need to handle this manually. */
s/start the inferior/call the function
> + if (gdbarch_shadow_stack_push_p (gdbarch))
> + gdbarch_shadow_stack_push (gdbarch, bp_addr, regcache);
>
> /* Set up a frame ID for the dummy frame so we can pass it to
> set_momentary_breakpoint. We need to give the breakpoint a frame
Otherwise looks OK to me.
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 08/11] gdb, gdbarch: Enable inferior calls for shadow stack support.
2025-06-19 9:25 ` Luis Machado
@ 2025-06-23 17:49 ` Schimpe, Christina
0 siblings, 0 replies; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-23 17:49 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
> -----Original Message-----
> From: Luis Machado <luis.machado@arm.com>
> Sent: Thursday, June 19, 2025 11:25 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> Subject: Re: [PATCH v4 08/11] gdb, gdbarch: Enable inferior calls for shadow
> stack support.
>
> On 6/17/25 13:11, Christina Schimpe wrote:
> > Inferior calls in GDB reset the current PC to the beginning of the
> > function that is called. As no call instruction is executed the new
> > return address needs to be pushed to the shadow stack and the shadow
> > stack pointer needs to be updated.
> >
> > This commit adds a new gdbarch method to push an address on the shadow
> > stack. The method is used to adapt the function
> 'call_function_by_hand_dummy'
> > for inferior call shadow stack support.
> >
> > Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> > ---
> > gdb/gdbarch-gen.c | 32 ++++++++++++++++++++++++++++++++
> > gdb/gdbarch-gen.h | 14 ++++++++++++++
> > gdb/gdbarch_components.py | 16 ++++++++++++++++
> > gdb/infcall.c | 14 ++++++++++----
> > 4 files changed, 72 insertions(+), 4 deletions(-)
> >
> > diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c index
> > 32d16598940..3ca19c427a3 100644
> > --- a/gdb/gdbarch-gen.c
> > +++ b/gdb/gdbarch-gen.c
> > @@ -262,6 +262,7 @@ struct gdbarch
> > gdbarch_read_core_file_mappings_ftype *read_core_file_mappings =
> default_read_core_file_mappings;
> > gdbarch_use_target_description_from_corefile_notes_ftype
> *use_target_description_from_corefile_notes =
> default_use_target_description_from_corefile_notes;
> > gdbarch_core_parse_exec_context_ftype *core_parse_exec_context =
> > default_core_parse_exec_context;
> > + gdbarch_shadow_stack_push_ftype *shadow_stack_push = nullptr;
> > };
> >
> > /* Create a new ``struct gdbarch'' based on information provided by
> > @@ -535,6 +536,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
> > /* Skip verify of read_core_file_mappings, invalid_p == 0. */
> > /* Skip verify of use_target_description_from_corefile_notes, invalid_p == 0.
> */
> > /* Skip verify of core_parse_exec_context, invalid_p == 0. */
> > + /* Skip verify of shadow_stack_push, has predicate. */
> > if (!log.empty ())
> > internal_error (_("verify_gdbarch: the following are invalid ...%s"),
> > log.c_str ());
> > @@ -1406,6 +1408,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct
> ui_file *file)
> > gdb_printf (file,
> > "gdbarch_dump: core_parse_exec_context = <%s>\n",
> > host_address_to_string (gdbarch->core_parse_exec_context));
> > + gdb_printf (file,
> > + "gdbarch_dump: gdbarch_shadow_stack_push_p() = %d\n",
> > + gdbarch_shadow_stack_push_p (gdbarch));
> > + gdb_printf (file,
> > + "gdbarch_dump: shadow_stack_push = <%s>\n",
> > + host_address_to_string (gdbarch->shadow_stack_push));
> > if (gdbarch->dump_tdep != NULL)
> > gdbarch->dump_tdep (gdbarch, file); } @@ -5551,3 +5559,27 @@
> > set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, {
> > gdbarch->core_parse_exec_context = core_parse_exec_context; }
> > +
> > +bool
> > +gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch) {
> > + gdb_assert (gdbarch != NULL);
> > + return gdbarch->shadow_stack_push != NULL; }
> > +
> > +void
> > +gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR
> > +new_addr, regcache *regcache) {
> > + gdb_assert (gdbarch != NULL);
> > + gdb_assert (gdbarch->shadow_stack_push != NULL);
> > + if (gdbarch_debug >= 2)
> > + gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_push called\n");
> > + gdbarch->shadow_stack_push (gdbarch, new_addr, regcache); }
> > +
> > +void
> > +set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
> > + gdbarch_shadow_stack_push_ftype
> shadow_stack_push) {
> > + gdbarch->shadow_stack_push = shadow_stack_push; }
> > diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index
> > 313a8f198fd..c276cd66d71 100644
> > --- a/gdb/gdbarch-gen.h
> > +++ b/gdb/gdbarch-gen.h
> > @@ -1801,3 +1801,17 @@ extern void
> > set_gdbarch_use_target_description_from_corefile_notes (struct gdbar
> > typedef core_file_exec_context (gdbarch_core_parse_exec_context_ftype)
> > (struct gdbarch *gdbarch, bfd *cbfd); extern core_file_exec_context
> > gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, bfd *cbfd);
> > extern void set_gdbarch_core_parse_exec_context (struct gdbarch
> > *gdbarch, gdbarch_core_parse_exec_context_ftype
> > *core_parse_exec_context);
> > +
> > +/* Some targets support special hardware-assisted control-flow protection
> > + technologies. For example, the Intel Control-Flow Enforcement Technology
> > + (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
> > + To enable inferior calls the function shadow_stack_push has to be
> provided.
> > +> + Push the address NEW_ADDR on the shadow stack and update the
> > +> + shadow stack
> > + pointer. */
> > +
> > +extern bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch);
> > +
> > +typedef void (gdbarch_shadow_stack_push_ftype) (struct gdbarch
> > +*gdbarch, CORE_ADDR new_addr, regcache *regcache); extern void
> > +gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR
> > +new_addr, regcache *regcache); extern void
> > +set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
> > +gdbarch_shadow_stack_push_ftype *shadow_stack_push);
> > diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
> > index ec09d955088..ab685b14ec7 100644
> > --- a/gdb/gdbarch_components.py
> > +++ b/gdb/gdbarch_components.py
> > @@ -2848,3 +2848,19 @@ which all assume current_inferior() is the one to
> read from.
> > predefault="default_core_parse_exec_context",
> > invalid=False,
> > )
> > +
> > +Method(
> > + comment="""
> > +Some targets support special hardware-assisted control-flow
> > +protection technologies. For example, the Intel Control-Flow
> > +Enforcement Technology (Intel CET) on x86 provides a shadow stack and
> indirect branch tracking.
> > +To enable inferior calls the function shadow_stack_push has to be provided.
>
> Maybe update the last part like so?
>
> "To enable shadow stack support for inferior calls the shadow_stack_push
> gdbarch hook has to be provided."
>
> > +
> > +Push the address NEW_ADDR on the shadow stack and update the shadow
> > +stack pointer.
> > +""",
>
> Maybe...
>
> "Push NEW_ADDR to the shadow stack..."?
>
> > + type="void",
> > + name="shadow_stack_push",
> > + params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")],
> > + predicate=True,
> > +)
> > diff --git a/gdb/infcall.c b/gdb/infcall.c index
> > 098072dfd2a..a1c80c81238 100644
> > --- a/gdb/infcall.c
> > +++ b/gdb/infcall.c
> > @@ -1448,10 +1448,16 @@ call_function_by_hand_dummy (struct value
> *function,
> > /* Create the dummy stack frame. Pass in the call dummy address as,
> > presumably, the ABI code knows where, in the call dummy, the
> > return address should be pointed. */
> > - sp = gdbarch_push_dummy_call (gdbarch, function,
> > - get_thread_regcache (inferior_thread ()),
> > - bp_addr, args.size (), args.data (),
> > - sp, return_method, struct_addr);
> > + regcache *regcache = get_thread_regcache (inferior_thread ());
> > + sp = gdbarch_push_dummy_call (gdbarch, function, regcache, bp_addr,
> > + args.size (), args.data (), sp,
> > + return_method, struct_addr);
> > +
> > + /* Push the return address of the inferior (bp_addr) on the shadow
> > + stack
>
> s/on the shadow stack/to the shadow stack
>
> > + and update the shadow stack pointer. As we don't execute a call
> > + instruction to start the inferior we need to handle this
> > + manually. */
>
> s/start the inferior/call the function
>
> > + if (gdbarch_shadow_stack_push_p (gdbarch))
> > + gdbarch_shadow_stack_push (gdbarch, bp_addr, regcache);
> >
> > /* Set up a frame ID for the dummy frame so we can pass it to
> > set_momentary_breakpoint. We need to give the breakpoint a
> > frame
>
> Otherwise looks OK to me.
>
> Reviewed-By: Luis Machado <luis.machado@arm.com>
Thanks for the review. I agree with all your comments and will fix them in v5.
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
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
` (7 preceding siblings ...)
2025-06-17 12:11 ` [PATCH v4 08/11] gdb, gdbarch: Enable inferior calls for shadow stack support Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-17 12:21 ` Eli Zaretskii
2025-06-19 9:25 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer Christina Schimpe
2025-06-17 12:11 ` [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux Christina Schimpe
10 siblings, 2 replies; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
This patch enables inferior calls to support Intel's Control-Flow
Enforcement Technology (CET), which provides the shadow stack feature
for the x86 architecture.
Following the restriction of the linux kernel, enable inferior calls
for amd64 only.
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
gdb/amd64-linux-tdep.c | 64 +++++++++++++++++++
gdb/doc/gdb.texinfo | 29 +++++++++
.../gdb.arch/amd64-shadow-stack-cmds.exp | 55 +++++++++++++++-
3 files changed, 147 insertions(+), 1 deletion(-)
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index 9436f0b190c..d847248659a 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -1931,6 +1931,68 @@ amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
return (binfo->bits_per_word / binfo->bits_per_byte);
}
+/* Read the shadow stack pointer register and return its value, if
+ possible. */
+
+static std::optional<CORE_ADDR>
+amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache)
+{
+ const i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
+
+ if (tdep == nullptr || tdep->ssp_regnum < 0)
+ return {};
+
+ CORE_ADDR ssp;
+ if (regcache_raw_read_unsigned (regcache, tdep->ssp_regnum, &ssp)
+ != REG_VALID)
+ return {};
+
+ /* Starting with v6.6., the Linux kernel supports CET shadow stack.
+ Dependent on the target the ssp register can be invalid or nullptr
+ when shadow stack is supported by HW and the linux kernel but not
+ enabled for the current thread. */
+ if (ssp == 0x0)
+ return {};
+
+ return ssp;
+}
+
+/* If shadow stack is enabled, push the address NEW_ADDR on the shadow
+ stack and increment the shadow stack pointer accordingly. */
+
+static void
+amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr,
+ regcache *regcache)
+{
+ std::optional<CORE_ADDR> ssp
+ = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
+ if (!ssp.has_value ())
+ return;
+
+ /* The shadow stack grows downwards. To push addresses on the stack,
+ we need to decrement SSP. */
+ const int element_size
+ = amd64_linux_shadow_stack_element_size_aligned (gdbarch);
+ const CORE_ADDR new_ssp = *ssp - element_size;
+
+ /* Starting with v6.6., the Linux kernel supports CET shadow stack.
+ Using /proc/PID/smaps we can only check if NEW_SSP points to shadow
+ stack memory. If it doesn't, we assume the stack is full. */
+ std::pair<CORE_ADDR, CORE_ADDR> memrange;
+ if (!linux_address_in_shadow_stack_mem_range (new_ssp, &memrange))
+ error (_("No space left on the shadow stack."));
+
+ /* On x86 there can be a shadow stack token at bit 63. For x32, the
+ address size is only 32 bit. Thus, we must use ELEMENT_SIZE (and
+ not gdbarch_addr_bit) to determine the width of the address to be
+ written. */
+ const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ write_memory_unsigned_integer (new_ssp, element_size, byte_order,
+ (ULONGEST) new_addr);
+
+ i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
+ regcache_raw_write_unsigned (regcache, tdep->ssp_regnum, new_ssp);
+}
/* Implement shadow stack pointer unwinding. For each new shadow stack
pointer check if its address is still in the shadow stack memory range.
@@ -2057,6 +2119,8 @@ 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);
+
+ set_gdbarch_shadow_stack_push (gdbarch, amd64_linux_shadow_stack_push);
dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
}
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 0ae09f09c88..cf152bd1e6f 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -27033,6 +27033,35 @@ registers
@end itemize
+@subsubsection Intel Control-Flow Enforcement Technology.
+@cindex Intel Control-Flow Enforcement Technology.
+
+The @dfn{Intel Control-Flow Enforcement Technology} (@acronym{Intel CET})
+provides two capabilities to defend against ``Return-oriented Programming''
+and ``call/jmp-oriented programming'' style control-flow attacks:
+
+@itemize @bullet
+@item Shadow Stack:
+A shadow stack is a second stack for a program. It holds the return
+addresses pushed by the call instruction. The @code{RET} instruction pops the
+return addresses from both call and shadow stack. If the return addresses from
+the two stacks do not match, the processor signals a control protection
+exception.
+@item Indirect Branch Tracking (IBT):
+When IBT is enabled, the CPU implements a state machine that tracks indirect
+@code{JMP} and @code{CALL} instructions. The state machine can be either IDLE
+or WAIT_FOR_ENDBRANCH. In WAIT_FOR_ENDBRANCH state the next instruction in
+the program stream must be an @code{ENDBR} instruction, otherwise the
+processor signals a control protection exception.
+@end itemize
+
+Impact on Call/Print:
+Inferior calls in @value{GDBN} reset the current PC to the beginning of the
+function that is called. No call instruction is executed, but the @code{RET}
+instruction actually is. To avoid a control protection exception due to the
+missing return address on the shadow stack, @value{GDBN} pushes the new return
+address to the shadow stack and updates the shadow stack pointer.
+
@node Alpha
@subsection Alpha
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
index 17f32ce3964..96f83678f39 100644
--- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
@@ -13,12 +13,29 @@
# 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.
+# Test shadow stack enabling for frame level update, the return and the
+# call command.
+# As potential CET violations often only occur after resuming normal
+# execution, test normal program continuation after each return or call
+# commands.
require allow_ssp_tests
standard_testfile amd64-shadow-stack.c
+proc restart_and_run_infcall_call2 {} {
+ global binfile
+ clean_restart ${binfile}
+ if { ![runto_main] } {
+ return -1
+ }
+ set inside_infcall_str "The program being debugged stopped while in a function called from GDB"
+ gdb_breakpoint [ gdb_get_line_number "break call2" ]
+ gdb_continue_to_breakpoint "break call2" ".*break call2.*"
+ gdb_test "call (int) call2()" \
+ "Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*"
+}
+
save_vars { ::env(GLIBC_TUNABLES) } {
append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
@@ -33,6 +50,42 @@ save_vars { ::env(GLIBC_TUNABLES) } {
return -1
}
+ with_test_prefix "test inferior call and continue" {
+ gdb_breakpoint [ gdb_get_line_number "break call1" ]
+ gdb_continue_to_breakpoint "break call1" ".*break call1.*"
+
+ gdb_test "call (int) call2()" "= 42"
+
+ gdb_continue_to_end
+ }
+
+ with_test_prefix "test return inside an inferior call" {
+ restart_and_run_infcall_call2
+
+ gdb_test "return" "\#0.*call2.*" \
+ "Test shadow stack return inside an inferior call" \
+ "Make.*return now\\? \\(y or n\\) " "y"
+
+ gdb_continue_to_end
+ }
+
+ with_test_prefix "test return 'above' an inferior call" {
+ restart_and_run_infcall_call2
+
+ gdb_test "frame 2" "call2 ().*" "move to frame 'above' inferior call"
+
+ gdb_test "return" "\#0.*call1.*" \
+ "Test shadow stack return 'above' an inferior call" \
+ "Make.*return now\\? \\(y or n\\) " "y"
+
+ gdb_continue_to_end
+ }
+
+ 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" ]
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls.
2025-06-17 12:11 ` [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls Christina Schimpe
@ 2025-06-17 12:21 ` Eli Zaretskii
2025-06-19 9:25 ` Luis Machado
1 sibling, 0 replies; 53+ messages in thread
From: Eli Zaretskii @ 2025-06-17 12:21 UTC (permalink / raw)
To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann
> From: Christina Schimpe <christina.schimpe@intel.com>
> Cc: thiago.bauermann@linaro.org,
> eliz@gnu.org
> Date: Tue, 17 Jun 2025 12:11:45 +0000
>
> This patch enables inferior calls to support Intel's Control-Flow
> Enforcement Technology (CET), which provides the shadow stack feature
> for the x86 architecture.
> Following the restriction of the linux kernel, enable inferior calls
> for amd64 only.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> gdb/amd64-linux-tdep.c | 64 +++++++++++++++++++
> gdb/doc/gdb.texinfo | 29 +++++++++
> .../gdb.arch/amd64-shadow-stack-cmds.exp | 55 +++++++++++++++-
> 3 files changed, 147 insertions(+), 1 deletion(-)
Thanks, the documentation part is okay.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls.
2025-06-17 12:11 ` [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls Christina Schimpe
2025-06-17 12:21 ` Eli Zaretskii
@ 2025-06-19 9:25 ` Luis Machado
2025-06-27 19:52 ` Schimpe, Christina
1 sibling, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:25 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> This patch enables inferior calls to support Intel's Control-Flow
> Enforcement Technology (CET), which provides the shadow stack feature
> for the x86 architecture.
> Following the restriction of the linux kernel, enable inferior calls
> for amd64 only.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> gdb/amd64-linux-tdep.c | 64 +++++++++++++++++++
> gdb/doc/gdb.texinfo | 29 +++++++++
> .../gdb.arch/amd64-shadow-stack-cmds.exp | 55 +++++++++++++++-
> 3 files changed, 147 insertions(+), 1 deletion(-)
>
> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> index 9436f0b190c..d847248659a 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -1931,6 +1931,68 @@ amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
> return (binfo->bits_per_word / binfo->bits_per_byte);
> }
>
> +/* Read the shadow stack pointer register and return its value, if
> + possible. */
> +
> +static std::optional<CORE_ADDR>
> +amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache)
> +{
> + const i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
> +
> + if (tdep == nullptr || tdep->ssp_regnum < 0)
> + return {};
> +
> + CORE_ADDR ssp;
> + if (regcache_raw_read_unsigned (regcache, tdep->ssp_regnum, &ssp)
> + != REG_VALID)
> + return {};
> +
> + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
Typo, period after "v6.6".
> + Dependent on the target the ssp register can be invalid or nullptr
> + when shadow stack is supported by HW and the linux kernel but not
> + enabled for the current thread. */
I feel the comment doesn't quite reflect this case very well. We don't have a
nullptr here, so I suppose ssp == 0x0 means ssp is unavailable? We should make
that clear here, but we don't need to have this more generic comment pasted
here again, as it was explained elsewhere already.
> + if (ssp == 0x0)
> + return {};
> +
> + return ssp;
> +}
> +
> +/* If shadow stack is enabled, push the address NEW_ADDR on the shadow
s/the address NEW_ADDR on/NEW_ADDR to
> + stack and increment the shadow stack pointer accordingly. */
> +
> +static void
> +amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr,
> + regcache *regcache)
> +{
> + std::optional<CORE_ADDR> ssp
> + = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
> + if (!ssp.has_value ())
> + return;
> +
> + /* The shadow stack grows downwards. To push addresses on the stack,
s/on the/to the
> + we need to decrement SSP. */
> + const int element_size
> + = amd64_linux_shadow_stack_element_size_aligned (gdbarch);
> + const CORE_ADDR new_ssp = *ssp - element_size;
> +
> + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
Same typo, period after "v6.6". I feel this comment has been repeated enough
times throughout the code. If it is available at a visible location, I think
we can do without it elsewhere.
For instance, we could even mention the
kernel version in the news entry, or at the gdbarch initialization code when
we are fetching a target description. Then it should be enough.
> + Using /proc/PID/smaps we can only check if NEW_SSP points to shadow
> + stack memory. If it doesn't, we assume the stack is full. */
> + std::pair<CORE_ADDR, CORE_ADDR> memrange;
> + if (!linux_address_in_shadow_stack_mem_range (new_ssp, &memrange))
> + error (_("No space left on the shadow stack."));
> +
> + /* On x86 there can be a shadow stack token at bit 63. For x32, the
> + address size is only 32 bit. Thus, we must use ELEMENT_SIZE (and
> + not gdbarch_addr_bit) to determine the width of the address to be
> + written. */
> + const bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> + write_memory_unsigned_integer (new_ssp, element_size, byte_order,
> + (ULONGEST) new_addr);
> +
> + i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
> + regcache_raw_write_unsigned (regcache, tdep->ssp_regnum, new_ssp);
> +}
>
> /* Implement shadow stack pointer unwinding. For each new shadow stack
> pointer check if its address is still in the shadow stack memory range.
> @@ -2057,6 +2119,8 @@ 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);
> +
> + set_gdbarch_shadow_stack_push (gdbarch, amd64_linux_shadow_stack_push);
> dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
> }
>
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 0ae09f09c88..cf152bd1e6f 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -27033,6 +27033,35 @@ registers
>
> @end itemize
>
> +@subsubsection Intel Control-Flow Enforcement Technology.
> +@cindex Intel Control-Flow Enforcement Technology.
> +
> +The @dfn{Intel Control-Flow Enforcement Technology} (@acronym{Intel CET})
> +provides two capabilities to defend against ``Return-oriented Programming''
> +and ``call/jmp-oriented programming'' style control-flow attacks:
> +
> +@itemize @bullet
> +@item Shadow Stack:
> +A shadow stack is a second stack for a program. It holds the return
> +addresses pushed by the call instruction. The @code{RET} instruction pops the
> +return addresses from both call and shadow stack. If the return addresses from
> +the two stacks do not match, the processor signals a control protection
> +exception.
> +@item Indirect Branch Tracking (IBT):
> +When IBT is enabled, the CPU implements a state machine that tracks indirect
> +@code{JMP} and @code{CALL} instructions. The state machine can be either IDLE
> +or WAIT_FOR_ENDBRANCH. In WAIT_FOR_ENDBRANCH state the next instruction in
> +the program stream must be an @code{ENDBR} instruction, otherwise the
> +processor signals a control protection exception.
> +@end itemize
> +
> +Impact on Call/Print:
> +Inferior calls in @value{GDBN} reset the current PC to the beginning of the
> +function that is called. No call instruction is executed, but the @code{RET}
> +instruction actually is. To avoid a control protection exception due to the
> +missing return address on the shadow stack, @value{GDBN} pushes the new return
> +address to the shadow stack and updates the shadow stack pointer.
> +
> @node Alpha
> @subsection Alpha
>
> diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> index 17f32ce3964..96f83678f39 100644
> --- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp
> @@ -13,12 +13,29 @@
> # 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.
> +# Test shadow stack enabling for frame level update, the return and the
> +# call command.
s/command/commands
> +# As potential CET violations often only occur after resuming normal
> +# execution, test normal program continuation after each return or call
> +# commands.
>
> require allow_ssp_tests
>
> standard_testfile amd64-shadow-stack.c
>
> +proc restart_and_run_infcall_call2 {} {
> + global binfile
> + clean_restart ${binfile}
> + if { ![runto_main] } {
> + return -1
> + }
> + set inside_infcall_str "The program being debugged stopped while in a function called from GDB"
> + gdb_breakpoint [ gdb_get_line_number "break call2" ]
> + gdb_continue_to_breakpoint "break call2" ".*break call2.*"
> + gdb_test "call (int) call2()" \
> + "Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*"
> +}
> +
> save_vars { ::env(GLIBC_TUNABLES) } {
>
> append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
> @@ -33,6 +50,42 @@ save_vars { ::env(GLIBC_TUNABLES) } {
> return -1
> }
>
> + with_test_prefix "test inferior call and continue" {
> + gdb_breakpoint [ gdb_get_line_number "break call1" ]
> + gdb_continue_to_breakpoint "break call1" ".*break call1.*"
> +
> + gdb_test "call (int) call2()" "= 42"
> +
> + gdb_continue_to_end
> + }
> +
> + with_test_prefix "test return inside an inferior call" {
> + restart_and_run_infcall_call2
> +
> + gdb_test "return" "\#0.*call2.*" \
> + "Test shadow stack return inside an inferior call" \
> + "Make.*return now\\? \\(y or n\\) " "y"
> +
> + gdb_continue_to_end
> + }
> +
> + with_test_prefix "test return 'above' an inferior call" {
> + restart_and_run_infcall_call2
> +
> + gdb_test "frame 2" "call2 ().*" "move to frame 'above' inferior call"
> +
> + gdb_test "return" "\#0.*call1.*" \
> + "Test shadow stack return 'above' an inferior call" \
> + "Make.*return now\\? \\(y or n\\) " "y"
> +
> + gdb_continue_to_end
> + }
> +
> + 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" ]
>
Otherwise this is OK.
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls.
2025-06-19 9:25 ` Luis Machado
@ 2025-06-27 19:52 ` Schimpe, Christina
2025-06-28 10:38 ` Luis Machado
0 siblings, 1 reply; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-27 19:52 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
Hi Luis,
Thanks for the review.
<...>
> > +
> > + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
>
> Typo, period after "v6.6".
>
> > + Dependent on the target the ssp register can be invalid or nullptr
> > + when shadow stack is supported by HW and the linux kernel but not
> > + enabled for the current thread. */
>
> I feel the comment doesn't quite reflect this case very well. We don't have a
> nullptr here, so I suppose ssp == 0x0 means ssp is unavailable? We should
> make that clear here, but we don't need to have this more generic comment
> pasted here again, as it was explained elsewhere already.
I agree. I removed the "Starting with v6.6., the Linux kernel supports..." comment
and rewrote the sentence afterwards as follows:
"Dependent on the target in case the shadow stack pointer is unavailable, the ssp
register can be invalid or 0x0."
I hope this is a bit better.
> > + if (ssp == 0x0)
> > + return {};
> > +
> > + return ssp;
> > +}
> > +
> > +/* If shadow stack is enabled, push the address NEW_ADDR on the
> > +shadow
>
> s/the address NEW_ADDR on/NEW_ADDR to
>
> > + stack and increment the shadow stack pointer accordingly. */
> > +
> > +static void
> > +amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR
> new_addr,
> > + regcache *regcache)
> > +{
> > + std::optional<CORE_ADDR> ssp
> > + = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
> > + if (!ssp.has_value ())
> > + return;
> > +
> > + /* The shadow stack grows downwards. To push addresses on the
> > + stack,
>
> s/on the/to the
>
> > + we need to decrement SSP. */
> > + const int element_size
> > + = amd64_linux_shadow_stack_element_size_aligned (gdbarch); const
> > + CORE_ADDR new_ssp = *ssp - element_size;
> > +
> > + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
>
> Same typo, period after "v6.6". I feel this comment has been repeated
> enough times throughout the code. If it is available at a visible location, I
> think we can do without it elsewhere.
I agree.
> For instance, we could even mention the
> kernel version in the news entry, or at the gdbarch initialization code when
> we are fetching a target description. Then it should be enough.
I would suggest to remove the comment
"Starting with v6.6., the Linux kernel supports CET shadow stack."
everywhere except in the allow_ssp_tests procedure, as it fits best there in my opinion.
Would that be acceptable?
<...>
> Otherwise this is OK.
>
> Reviewed-By: Luis Machado <luis.machado@arm.com>
I actually missed to reply to this feedback in the first place. Sorry for the late reply.
I'll post my v5 soon, as I'll be out for one week. If anything suggested above is not acceptable,
it would be great if you could let me know.
Since this is affecting comments only, I think it should be straight-forward to fix.
For now I'll post my v5 as suggested above.
Kind Regards,
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls.
2025-06-27 19:52 ` Schimpe, Christina
@ 2025-06-28 10:38 ` Luis Machado
2025-06-28 20:03 ` Thiago Jung Bauermann
0 siblings, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-28 10:38 UTC (permalink / raw)
To: Schimpe, Christina, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/27/25 20:52, Schimpe, Christina wrote:
> Hi Luis,
>
> Thanks for the review.
>
> <...>
>
>>> +
>>> + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
>>
>> Typo, period after "v6.6".
>>
>>> + Dependent on the target the ssp register can be invalid or nullptr
>>> + when shadow stack is supported by HW and the linux kernel but not
>>> + enabled for the current thread. */
>>
>> I feel the comment doesn't quite reflect this case very well. We don't have a
>> nullptr here, so I suppose ssp == 0x0 means ssp is unavailable? We should
>> make that clear here, but we don't need to have this more generic comment
>> pasted here again, as it was explained elsewhere already.
>
> I agree. I removed the "Starting with v6.6., the Linux kernel supports..." comment
> and rewrote the sentence afterwards as follows:
>
> "Dependent on the target in case the shadow stack pointer is unavailable, the ssp
> register can be invalid or 0x0."
>
> I hope this is a bit better.
>
That sounds better to me. Thanks.
>>> + if (ssp == 0x0)
>>> + return {};
>>> +
>>> + return ssp;
>>> +}
>>> +
>>> +/* If shadow stack is enabled, push the address NEW_ADDR on the
>>> +shadow
>>
>> s/the address NEW_ADDR on/NEW_ADDR to
>>
>>> + stack and increment the shadow stack pointer accordingly. */
>>> +
>>> +static void
>>> +amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR
>> new_addr,
>>> + regcache *regcache)
>>> +{
>>> + std::optional<CORE_ADDR> ssp
>>> + = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
>>> + if (!ssp.has_value ())
>>> + return;
>>> +
>>> + /* The shadow stack grows downwards. To push addresses on the
>>> + stack,
>>
>> s/on the/to the
>>
>>> + we need to decrement SSP. */
>>> + const int element_size
>>> + = amd64_linux_shadow_stack_element_size_aligned (gdbarch); const
>>> + CORE_ADDR new_ssp = *ssp - element_size;
>>> +
>>> + /* Starting with v6.6., the Linux kernel supports CET shadow stack.
>>
>> Same typo, period after "v6.6". I feel this comment has been repeated
>> enough times throughout the code. If it is available at a visible location, I
>> think we can do without it elsewhere.
>
> I agree.
>
>> For instance, we could even mention the
>> kernel version in the news entry, or at the gdbarch initialization code when
>> we are fetching a target description. Then it should be enough.
>
> I would suggest to remove the comment
>
> "Starting with v6.6., the Linux kernel supports CET shadow stack."
>
> everywhere except in the allow_ssp_tests procedure, as it fits best there in my opinion.
>
> Would that be acceptable?
I think so. If you want to mention it somewhere in the gdb sources, then maybe the gdbarch initialization
function would be another acceptable spot.
>
> <...>
>
>> Otherwise this is OK.
>>
>> Reviewed-By: Luis Machado <luis.machado@arm.com>
>
> I actually missed to reply to this feedback in the first place. Sorry for the late reply.
> I'll post my v5 soon, as I'll be out for one week. If anything suggested above is not acceptable,
> it would be great if you could let me know.
> Since this is affecting comments only, I think it should be straight-forward to fix.
> For now I'll post my v5 as suggested above.
No worries. I see v5 on the list and will go through it. Have a nice break.
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls.
2025-06-28 10:38 ` Luis Machado
@ 2025-06-28 20:03 ` Thiago Jung Bauermann
2025-06-28 21:05 ` Thiago Jung Bauermann
0 siblings, 1 reply; 53+ messages in thread
From: Thiago Jung Bauermann @ 2025-06-28 20:03 UTC (permalink / raw)
To: Luis Machado; +Cc: Schimpe, Christina, gdb-patches, eliz
Luis Machado <luis.machado@arm.com> writes:
> On 6/27/25 20:52, Schimpe, Christina wrote:
>> I'll post my v5 soon, as I'll be out for one week. If anything suggested above is not
>> acceptable,
>> it would be great if you could let me know.
>> Since this is affecting comments only, I think it should be straight-forward to fix.
>> For now I'll post my v5 as suggested above.
>
> No worries. I see v5 on the list and will go through it. Have a nice break.
FYI I just tested my GCS v3 patches on top of CET v5 and they apply
cleanly (modulo the duplicate core_find change in
gdb/testsuite/lib/gdb.exp),
and also all aarch64-gcs*.exp tests pass so CET v5 is good from my perspective.
--
Thiago
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls.
2025-06-28 20:03 ` Thiago Jung Bauermann
@ 2025-06-28 21:05 ` Thiago Jung Bauermann
0 siblings, 0 replies; 53+ messages in thread
From: Thiago Jung Bauermann @ 2025-06-28 21:05 UTC (permalink / raw)
To: Luis Machado; +Cc: Schimpe, Christina, gdb-patches, eliz
Thiago Jung Bauermann <thiago.bauermann@linaro.org> writes:
> Luis Machado <luis.machado@arm.com> writes:
>
>> On 6/27/25 20:52, Schimpe, Christina wrote:
>>> I'll post my v5 soon, as I'll be out for one week. If anything suggested above is not
>>> acceptable,
>>> it would be great if you could let me know.
>>> Since this is affecting comments only, I think it should be straight-forward to fix.
>>> For now I'll post my v5 as suggested above.
>>
>> No worries. I see v5 on the list and will go through it. Have a nice break.
>
> FYI I just tested my GCS v3 patches on top of CET v5 and they apply
> cleanly (modulo the duplicate core_find change in
> gdb/testsuite/lib/gdb.exp),
> and also all aarch64-gcs*.exp tests pass so CET v5 is good from my perspective.
And, of course, thank you for sending v5!
--
Thiago
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
` (8 preceding siblings ...)
2025-06-17 12:11 ` [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-17 18:16 ` Tom Tromey
2025-06-19 9:26 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux Christina Schimpe
10 siblings, 2 replies; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
This patch is required by the following commit.
Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
gdb/arch-utils.c | 10 ++++++++++
gdb/arch-utils.h | 5 +++++
gdb/gdbarch-gen.c | 22 ++++++++++++++++++++++
gdb/gdbarch-gen.h | 10 ++++++++++
gdb/gdbarch_components.py | 15 +++++++++++++++
5 files changed, 62 insertions(+)
diff --git a/gdb/arch-utils.c b/gdb/arch-utils.c
index be0494fba1e..c86df72a6d8 100644
--- a/gdb/arch-utils.c
+++ b/gdb/arch-utils.c
@@ -1218,6 +1218,16 @@ default_gdbarch_return_value
readbuf, writebuf);
}
+/* See arch-utils.h. */
+
+std::optional<CORE_ADDR>
+default_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
+ bool &shadow_stack_enabled)
+{
+ shadow_stack_enabled = false;
+ return {};
+}
+
obstack *gdbarch_obstack (gdbarch *arch)
{
return &arch->obstack;
diff --git a/gdb/arch-utils.h b/gdb/arch-utils.h
index 1509cb7441e..14a84b74733 100644
--- a/gdb/arch-utils.h
+++ b/gdb/arch-utils.h
@@ -414,4 +414,9 @@ extern enum return_value_convention default_gdbarch_return_value
struct regcache *regcache, struct value **read_value,
const gdb_byte *writebuf);
+/* Default implementation of gdbarch default_get_shadow_stack_pointer
+ method. */
+extern std::optional<CORE_ADDR> default_get_shadow_stack_pointer
+ (gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
+
#endif /* GDB_ARCH_UTILS_H */
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index 3ca19c427a3..4490d53a940 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -263,6 +263,7 @@ struct gdbarch
gdbarch_use_target_description_from_corefile_notes_ftype *use_target_description_from_corefile_notes = default_use_target_description_from_corefile_notes;
gdbarch_core_parse_exec_context_ftype *core_parse_exec_context = default_core_parse_exec_context;
gdbarch_shadow_stack_push_ftype *shadow_stack_push = nullptr;
+ gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer = default_get_shadow_stack_pointer;
};
/* Create a new ``struct gdbarch'' based on information provided by
@@ -537,6 +538,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
/* Skip verify of use_target_description_from_corefile_notes, invalid_p == 0. */
/* Skip verify of core_parse_exec_context, invalid_p == 0. */
/* Skip verify of shadow_stack_push, has predicate. */
+ /* Skip verify of get_shadow_stack_pointer, invalid_p == 0. */
if (!log.empty ())
internal_error (_("verify_gdbarch: the following are invalid ...%s"),
log.c_str ());
@@ -1414,6 +1416,9 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
gdb_printf (file,
"gdbarch_dump: shadow_stack_push = <%s>\n",
host_address_to_string (gdbarch->shadow_stack_push));
+ gdb_printf (file,
+ "gdbarch_dump: get_shadow_stack_pointer = <%s>\n",
+ host_address_to_string (gdbarch->get_shadow_stack_pointer));
if (gdbarch->dump_tdep != NULL)
gdbarch->dump_tdep (gdbarch, file);
}
@@ -5583,3 +5588,20 @@ set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
{
gdbarch->shadow_stack_push = shadow_stack_push;
}
+
+std::optional<CORE_ADDR>
+gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled)
+{
+ gdb_assert (gdbarch != NULL);
+ gdb_assert (gdbarch->get_shadow_stack_pointer != NULL);
+ if (gdbarch_debug >= 2)
+ gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer called\n");
+ return gdbarch->get_shadow_stack_pointer (gdbarch, regcache, shadow_stack_enabled);
+}
+
+void
+set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch,
+ gdbarch_get_shadow_stack_pointer_ftype get_shadow_stack_pointer)
+{
+ gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer;
+}
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index c276cd66d71..b8d1df94f59 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1806,6 +1806,8 @@ extern void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarc
technologies. For example, the Intel Control-Flow Enforcement Technology
(Intel CET) on x86 provides a shadow stack and indirect branch tracking.
To enable inferior calls the function shadow_stack_push has to be provided.
+ The method get_shadow_stack_pointer has to be provided to enable displaced
+ stepping.
Push the address NEW_ADDR on the shadow stack and update the shadow stack
pointer. */
@@ -1815,3 +1817,11 @@ extern bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch);
typedef void (gdbarch_shadow_stack_push_ftype) (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
extern void gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
extern void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shadow_stack_push_ftype *shadow_stack_push);
+
+/* If possible, return the shadow stack pointer. On some architectures, the
+ shadow stack pointer is available even if the feature is disabled. To
+ return the shadow stack enablement state configure SHADOW_STACK_ENABLED. */
+
+typedef std::optional<CORE_ADDR> (gdbarch_get_shadow_stack_pointer_ftype) (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
+extern std::optional<CORE_ADDR> gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
+extern void set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer);
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index ab685b14ec7..ccbdcaf07c9 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2855,6 +2855,8 @@ Some targets support special hardware-assisted control-flow protection
technologies. For example, the Intel Control-Flow Enforcement Technology
(Intel CET) on x86 provides a shadow stack and indirect branch tracking.
To enable inferior calls the function shadow_stack_push has to be provided.
+The method get_shadow_stack_pointer has to be provided to enable displaced
+stepping.
Push the address NEW_ADDR on the shadow stack and update the shadow stack
pointer.
@@ -2864,3 +2866,16 @@ pointer.
params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")],
predicate=True,
)
+
+Method(
+ comment="""
+If possible, return the shadow stack pointer. On some architectures, the
+shadow stack pointer is available even if the feature is disabled. To
+return the shadow stack enablement state configure SHADOW_STACK_ENABLED.
+""",
+ type="std::optional<CORE_ADDR>",
+ name="get_shadow_stack_pointer",
+ params=[("regcache *", "regcache"), ("bool &", "shadow_stack_enabled")],
+ predefault="default_get_shadow_stack_pointer",
+ invalid=False,
+)
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer.
2025-06-17 12:11 ` [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer Christina Schimpe
@ 2025-06-17 18:16 ` Tom Tromey
2025-06-20 12:59 ` Schimpe, Christina
2025-06-19 9:26 ` Luis Machado
1 sibling, 1 reply; 53+ messages in thread
From: Tom Tromey @ 2025-06-17 18:16 UTC (permalink / raw)
To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann, eliz
>>>>> "Christina" == Christina Schimpe <christina.schimpe@intel.com> writes:
Christina> +Method(
Christina> + comment="""
Christina> +If possible, return the shadow stack pointer. On some architectures, the
Christina> +shadow stack pointer is available even if the feature is disabled. To
Christina> +return the shadow stack enablement state configure SHADOW_STACK_ENABLED.
Christina> +""",
This wasn't super clear to me, but reading patch 11 made me believe that
the API is to set shadow_stack_enabled to true if shadow stacks are
enabled. Is that correct? If so could you update the comment to say
that?
You don't have to re-send the whole series just for this change fwiw.
thanks,
Tom
^ permalink raw reply [flat|nested] 53+ messages in thread
* RE: [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer.
2025-06-17 18:16 ` Tom Tromey
@ 2025-06-20 12:59 ` Schimpe, Christina
0 siblings, 0 replies; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-20 12:59 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches, thiago.bauermann, eliz
Hi Tom,
> -----Original Message-----
> From: Tom Tromey <tom@tromey.com>
> Sent: Tuesday, June 17, 2025 8:16 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org;
> eliz@gnu.org
> Subject: Re: [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get
> the shadow stack pointer.
>
> >>>>> "Christina" == Christina Schimpe <christina.schimpe@intel.com>
> writes:
>
> Christina> +Method(
> Christina> + comment="""
> Christina> +If possible, return the shadow stack pointer. On some
> Christina> +architectures, the shadow stack pointer is available even if
> Christina> +the feature is disabled. To return the shadow stack enablement
> state configure SHADOW_STACK_ENABLED.
> Christina> +""",
>
> This wasn't super clear to me, but reading patch 11 made me believe that the
> API is to set shadow_stack_enabled to true if shadow stacks are enabled. Is
> that correct? If so could you update the comment to say that?
Yes, this is correct. I'd update it as follows, to make it more clear hopefully:
"If possible, return the shadow stack pointer. On some architectures, the
shadow stack pointer is available even if the feature is disabled. To
return the feature's enablement state configure SHADOW_STACK_ENABLED.
Set it to true in case the shadow stack is enabled."
Thanks!
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
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer.
2025-06-17 12:11 ` [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer Christina Schimpe
2025-06-17 18:16 ` Tom Tromey
@ 2025-06-19 9:26 ` Luis Machado
2025-06-23 18:00 ` Schimpe, Christina
1 sibling, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:26 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> This patch is required by the following commit.
What particular entry in the series uses this? The displaced stepping one?
If so, we should make it more explicit.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
> gdb/arch-utils.c | 10 ++++++++++
> gdb/arch-utils.h | 5 +++++
> gdb/gdbarch-gen.c | 22 ++++++++++++++++++++++
> gdb/gdbarch-gen.h | 10 ++++++++++
> gdb/gdbarch_components.py | 15 +++++++++++++++
> 5 files changed, 62 insertions(+)
>
> diff --git a/gdb/arch-utils.c b/gdb/arch-utils.c
> index be0494fba1e..c86df72a6d8 100644
> --- a/gdb/arch-utils.c
> +++ b/gdb/arch-utils.c
> @@ -1218,6 +1218,16 @@ default_gdbarch_return_value
> readbuf, writebuf);
> }
>
> +/* See arch-utils.h. */
> +
> +std::optional<CORE_ADDR>
> +default_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
> + bool &shadow_stack_enabled)
> +{
> + shadow_stack_enabled = false;
> + return {};
> +}
> +
> obstack *gdbarch_obstack (gdbarch *arch)
> {
> return &arch->obstack;
> diff --git a/gdb/arch-utils.h b/gdb/arch-utils.h
> index 1509cb7441e..14a84b74733 100644
> --- a/gdb/arch-utils.h
> +++ b/gdb/arch-utils.h
> @@ -414,4 +414,9 @@ extern enum return_value_convention default_gdbarch_return_value
> struct regcache *regcache, struct value **read_value,
> const gdb_byte *writebuf);
>
> +/* Default implementation of gdbarch default_get_shadow_stack_pointer
> + method. */
> +extern std::optional<CORE_ADDR> default_get_shadow_stack_pointer
> + (gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
> +
> #endif /* GDB_ARCH_UTILS_H */
> diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
> index 3ca19c427a3..4490d53a940 100644
> --- a/gdb/gdbarch-gen.c
> +++ b/gdb/gdbarch-gen.c
> @@ -263,6 +263,7 @@ struct gdbarch
> gdbarch_use_target_description_from_corefile_notes_ftype *use_target_description_from_corefile_notes = default_use_target_description_from_corefile_notes;
> gdbarch_core_parse_exec_context_ftype *core_parse_exec_context = default_core_parse_exec_context;
> gdbarch_shadow_stack_push_ftype *shadow_stack_push = nullptr;
> + gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer = default_get_shadow_stack_pointer;
> };
>
> /* Create a new ``struct gdbarch'' based on information provided by
> @@ -537,6 +538,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
> /* Skip verify of use_target_description_from_corefile_notes, invalid_p == 0. */
> /* Skip verify of core_parse_exec_context, invalid_p == 0. */
> /* Skip verify of shadow_stack_push, has predicate. */
> + /* Skip verify of get_shadow_stack_pointer, invalid_p == 0. */
> if (!log.empty ())
> internal_error (_("verify_gdbarch: the following are invalid ...%s"),
> log.c_str ());
> @@ -1414,6 +1416,9 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
> gdb_printf (file,
> "gdbarch_dump: shadow_stack_push = <%s>\n",
> host_address_to_string (gdbarch->shadow_stack_push));
> + gdb_printf (file,
> + "gdbarch_dump: get_shadow_stack_pointer = <%s>\n",
> + host_address_to_string (gdbarch->get_shadow_stack_pointer));
> if (gdbarch->dump_tdep != NULL)
> gdbarch->dump_tdep (gdbarch, file);
> }
> @@ -5583,3 +5588,20 @@ set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
> {
> gdbarch->shadow_stack_push = shadow_stack_push;
> }
> +
> +std::optional<CORE_ADDR>
> +gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled)
> +{
> + gdb_assert (gdbarch != NULL);
> + gdb_assert (gdbarch->get_shadow_stack_pointer != NULL);
> + if (gdbarch_debug >= 2)
> + gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer called\n");
> + return gdbarch->get_shadow_stack_pointer (gdbarch, regcache, shadow_stack_enabled);
> +}
> +
> +void
> +set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch,
> + gdbarch_get_shadow_stack_pointer_ftype get_shadow_stack_pointer)
> +{
> + gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer;
> +}
> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
> index c276cd66d71..b8d1df94f59 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -1806,6 +1806,8 @@ extern void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarc
> technologies. For example, the Intel Control-Flow Enforcement Technology
> (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
> To enable inferior calls the function shadow_stack_push has to be provided.
> + The method get_shadow_stack_pointer has to be provided to enable displaced
> + stepping.
>
> Push the address NEW_ADDR on the shadow stack and update the shadow stack
> pointer. */
> @@ -1815,3 +1817,11 @@ extern bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch);
> typedef void (gdbarch_shadow_stack_push_ftype) (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
> extern void gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
> extern void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shadow_stack_push_ftype *shadow_stack_push);
> +
> +/* If possible, return the shadow stack pointer. On some architectures, the
> + shadow stack pointer is available even if the feature is disabled. To
> + return the shadow stack enablement state configure SHADOW_STACK_ENABLED. */
> +
> +typedef std::optional<CORE_ADDR> (gdbarch_get_shadow_stack_pointer_ftype) (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
> +extern std::optional<CORE_ADDR> gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
> +extern void set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer);
> diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
> index ab685b14ec7..ccbdcaf07c9 100644
> --- a/gdb/gdbarch_components.py
> +++ b/gdb/gdbarch_components.py
> @@ -2855,6 +2855,8 @@ Some targets support special hardware-assisted control-flow protection
> technologies. For example, the Intel Control-Flow Enforcement Technology
> (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
> To enable inferior calls the function shadow_stack_push has to be provided.
> +The method get_shadow_stack_pointer has to be provided to enable displaced
Maybe...
"The get_shadow_stack_pointer gdbarch hook has to be..."
> +stepping.
>
> Push the address NEW_ADDR on the shadow stack and update the shadow stack
> pointer.
> @@ -2864,3 +2866,16 @@ pointer.
> params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")],
> predicate=True,
> )
> +
> +Method(
> + comment="""
> +If possible, return the shadow stack pointer. On some architectures, the
> +shadow stack pointer is available even if the feature is disabled. To
> +return the shadow stack enablement state configure SHADOW_STACK_ENABLED.
> +""",
> + type="std::optional<CORE_ADDR>",
> + name="get_shadow_stack_pointer",
> + params=[("regcache *", "regcache"), ("bool &", "shadow_stack_enabled")],
> + predefault="default_get_shadow_stack_pointer",
> + invalid=False,
> +)
Otherwise looks OK.
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer.
2025-06-19 9:26 ` Luis Machado
@ 2025-06-23 18:00 ` Schimpe, Christina
0 siblings, 0 replies; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-23 18:00 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz, Tom Tromey
Hi Luis,
> -----Original Message-----
> From: Luis Machado <luis.machado@arm.com>
> Sent: Thursday, June 19, 2025 11:26 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> Subject: Re: [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get
> the shadow stack pointer.
>
> On 6/17/25 13:11, Christina Schimpe wrote:
> > This patch is required by the following commit.
>
> What particular entry in the series uses this? The displaced stepping one?
>
> If so, we should make it more explicit.
>
> >
> > Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> > ---
> > gdb/arch-utils.c | 10 ++++++++++
> > gdb/arch-utils.h | 5 +++++
> > gdb/gdbarch-gen.c | 22 ++++++++++++++++++++++
> > gdb/gdbarch-gen.h | 10 ++++++++++
> > gdb/gdbarch_components.py | 15 +++++++++++++++
> > 5 files changed, 62 insertions(+)
> >
> > diff --git a/gdb/arch-utils.c b/gdb/arch-utils.c index
> > be0494fba1e..c86df72a6d8 100644
> > --- a/gdb/arch-utils.c
> > +++ b/gdb/arch-utils.c
> > @@ -1218,6 +1218,16 @@ default_gdbarch_return_value
> > readbuf, writebuf);
> > }
> >
> > +/* See arch-utils.h. */
> > +
> > +std::optional<CORE_ADDR>
> > +default_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
> > + bool &shadow_stack_enabled)
> > +{
> > + shadow_stack_enabled = false;
> > + return {};
> > +}
> > +
> > obstack *gdbarch_obstack (gdbarch *arch) {
> > return &arch->obstack;
> > diff --git a/gdb/arch-utils.h b/gdb/arch-utils.h index
> > 1509cb7441e..14a84b74733 100644
> > --- a/gdb/arch-utils.h
> > +++ b/gdb/arch-utils.h
> > @@ -414,4 +414,9 @@ extern enum return_value_convention
> default_gdbarch_return_value
> > struct regcache *regcache, struct value **read_value,
> > const gdb_byte *writebuf);
> >
> > +/* Default implementation of gdbarch default_get_shadow_stack_pointer
> > + method. */
> > +extern std::optional<CORE_ADDR> default_get_shadow_stack_pointer
> > + (gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
> > +
> > #endif /* GDB_ARCH_UTILS_H */
> > diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c index
> > 3ca19c427a3..4490d53a940 100644
> > --- a/gdb/gdbarch-gen.c
> > +++ b/gdb/gdbarch-gen.c
> > @@ -263,6 +263,7 @@ struct gdbarch
> > gdbarch_use_target_description_from_corefile_notes_ftype
> *use_target_description_from_corefile_notes =
> default_use_target_description_from_corefile_notes;
> > gdbarch_core_parse_exec_context_ftype *core_parse_exec_context =
> default_core_parse_exec_context;
> > gdbarch_shadow_stack_push_ftype *shadow_stack_push = nullptr;
> > + gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer =
> > + default_get_shadow_stack_pointer;
> > };
> >
> > /* Create a new ``struct gdbarch'' based on information provided by
> > @@ -537,6 +538,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
> > /* Skip verify of use_target_description_from_corefile_notes, invalid_p == 0.
> */
> > /* Skip verify of core_parse_exec_context, invalid_p == 0. */
> > /* Skip verify of shadow_stack_push, has predicate. */
> > + /* Skip verify of get_shadow_stack_pointer, invalid_p == 0. */
> > if (!log.empty ())
> > internal_error (_("verify_gdbarch: the following are invalid ...%s"),
> > log.c_str ());
> > @@ -1414,6 +1416,9 @@ gdbarch_dump (struct gdbarch *gdbarch, struct
> ui_file *file)
> > gdb_printf (file,
> > "gdbarch_dump: shadow_stack_push = <%s>\n",
> > host_address_to_string (gdbarch->shadow_stack_push));
> > + gdb_printf (file,
> > + "gdbarch_dump: get_shadow_stack_pointer = <%s>\n",
> > + host_address_to_string (gdbarch->get_shadow_stack_pointer));
> > if (gdbarch->dump_tdep != NULL)
> > gdbarch->dump_tdep (gdbarch, file); } @@ -5583,3 +5588,20 @@
> > set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, {
> > gdbarch->shadow_stack_push = shadow_stack_push; }
> > +
> > +std::optional<CORE_ADDR>
> > +gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache
> > +*regcache, bool &shadow_stack_enabled) {
> > + gdb_assert (gdbarch != NULL);
> > + gdb_assert (gdbarch->get_shadow_stack_pointer != NULL);
> > + if (gdbarch_debug >= 2)
> > + gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer
> > +called\n");
> > + return gdbarch->get_shadow_stack_pointer (gdbarch, regcache,
> > +shadow_stack_enabled); }
> > +
> > +void
> > +set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch,
> > + gdbarch_get_shadow_stack_pointer_ftype
> > +get_shadow_stack_pointer) {
> > + gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer; }
> > diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index
> > c276cd66d71..b8d1df94f59 100644
> > --- a/gdb/gdbarch-gen.h
> > +++ b/gdb/gdbarch-gen.h
> > @@ -1806,6 +1806,8 @@ extern void set_gdbarch_core_parse_exec_context
> (struct gdbarch *gdbarch, gdbarc
> > technologies. For example, the Intel Control-Flow Enforcement Technology
> > (Intel CET) on x86 provides a shadow stack and indirect branch tracking.
> > To enable inferior calls the function shadow_stack_push has to be provided.
> > + The method get_shadow_stack_pointer has to be provided to enable
> displaced
> > + stepping.
> >
> > Push the address NEW_ADDR on the shadow stack and update the shadow
> stack
> > pointer. */
> > @@ -1815,3 +1817,11 @@ extern bool gdbarch_shadow_stack_push_p
> (struct
> > gdbarch *gdbarch); typedef void (gdbarch_shadow_stack_push_ftype)
> > (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache);
> > extern void gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
> > CORE_ADDR new_addr, regcache *regcache); extern void
> > set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch,
> > gdbarch_shadow_stack_push_ftype *shadow_stack_push);
> > +
> > +/* If possible, return the shadow stack pointer. On some architectures, the
> > + shadow stack pointer is available even if the feature is disabled. To
> > + return the shadow stack enablement state configure
> > +SHADOW_STACK_ENABLED. */
> > +
> > +typedef std::optional<CORE_ADDR>
> > +(gdbarch_get_shadow_stack_pointer_ftype) (struct gdbarch *gdbarch,
> > +regcache *regcache, bool &shadow_stack_enabled); extern
> > +std::optional<CORE_ADDR> gdbarch_get_shadow_stack_pointer (struct
> > +gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled);
> > +extern void set_gdbarch_get_shadow_stack_pointer (struct gdbarch
> > +*gdbarch, gdbarch_get_shadow_stack_pointer_ftype
> > +*get_shadow_stack_pointer);
> > diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
> > index ab685b14ec7..ccbdcaf07c9 100644
> > --- a/gdb/gdbarch_components.py
> > +++ b/gdb/gdbarch_components.py
> > @@ -2855,6 +2855,8 @@ Some targets support special hardware-assisted
> > control-flow protection technologies. For example, the Intel
> > Control-Flow Enforcement Technology (Intel CET) on x86 provides a shadow
> stack and indirect branch tracking.
> > To enable inferior calls the function shadow_stack_push has to be provided.
> > +The method get_shadow_stack_pointer has to be provided to enable
> > +displaced
>
> Maybe...
>
> "The get_shadow_stack_pointer gdbarch hook has to be..."
>
> > +stepping.
> >
> > Push the address NEW_ADDR on the shadow stack and update the shadow
> > stack pointer.
> > @@ -2864,3 +2866,16 @@ pointer.
> > params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")],
> > predicate=True,
> > )
> > +
> > +Method(
> > + comment="""
> > +If possible, return the shadow stack pointer. On some architectures,
> > +the shadow stack pointer is available even if the feature is
> > +disabled. To return the shadow stack enablement state configure
> SHADOW_STACK_ENABLED.
> > +""",
> > + type="std::optional<CORE_ADDR>",
> > + name="get_shadow_stack_pointer",
> > + params=[("regcache *", "regcache"), ("bool &", "shadow_stack_enabled")],
> > + predefault="default_get_shadow_stack_pointer",
> > + invalid=False,
> > +)
>
> Otherwise looks OK.
>
> Reviewed-By: Luis Machado <luis.machado@arm.com>
Thanks for the review. For v5, I will apply all your comments and also fix the comment
as suggested to Tom here, if there are no concerns:
https://sourceware.org/pipermail/gdb-patches/2025-June/218864.html
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
^ permalink raw reply [flat|nested] 53+ messages in thread
* [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux.
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
` (9 preceding siblings ...)
2025-06-17 12:11 ` [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer Christina Schimpe
@ 2025-06-17 12:11 ` Christina Schimpe
2025-06-17 12:22 ` Eli Zaretskii
2025-06-19 9:26 ` Luis Machado
10 siblings, 2 replies; 53+ messages in thread
From: Christina Schimpe @ 2025-06-17 12:11 UTC (permalink / raw)
To: gdb-patches; +Cc: thiago.bauermann, eliz
This patch enables displaced stepping to support Intel's Control-Flow
Enforcement Technology (CET), which provides the shadow stack feature
for the x86 architecture.
Following the restriction of the linux kernel, enable displaced stepping
for amd64 only.
If displaced stepping is active and the single stepped instruction is a
call instruction, the return address atop the stack is the address following
the copied instruction. However, to allow normal program execution it has
to be the address following the original instruction. Due to that reason,
the return address is corrected in amd64_displaced_step_fixup and
i386_displaced_step_fixup.
To avoid a control-protection exception if shadow stack is active,
the shadow stack top address must be corrected as well.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
gdb/NEWS | 3 +
gdb/amd64-linux-tdep.c | 16 +++-
gdb/amd64-tdep.c | 15 ++++
gdb/doc/gdb.texinfo | 11 ++-
gdb/i386-tdep.c | 15 ++++
.../gdb.arch/amd64-shadow-stack-disp-step.exp | 90 +++++++++++++++++++
6 files changed, 147 insertions(+), 3 deletions(-)
create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index b8fb7f0e484..15d2772230e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
*** Changes since GDB 16
+* Debugging Linux programs that use x86-64 or x86-64 with 32-bit pointer
+ size (X32) Shadow Stacks are now supported.
+
* Support for the shadow stack pointer register on x86-64 or x86-64 with
32-bit pointer size (X32) GNU/Linux.
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index d847248659a..f989cfb3bf8 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -1935,8 +1935,10 @@ amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
possible. */
static std::optional<CORE_ADDR>
-amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache)
+amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
+ bool &shadow_stack_enabled)
{
+ shadow_stack_enabled = false;
const i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
if (tdep == nullptr || tdep->ssp_regnum < 0)
@@ -1954,6 +1956,9 @@ amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache)
if (ssp == 0x0)
return {};
+ /* In case there is a shadow stack pointer available which is non-null,
+ the shadow stack feature is enabled. */
+ shadow_stack_enabled = true;
return ssp;
}
@@ -1964,8 +1969,13 @@ static void
amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr,
regcache *regcache)
{
+ bool shadow_stack_enabled;
std::optional<CORE_ADDR> ssp
- = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
+ = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache,
+ shadow_stack_enabled);
+
+ /* It's enough to check if SSP is valid as for amd64 linux shadow stack
+ is always enabled if SSP has a value. */
if (!ssp.has_value ())
return;
@@ -2121,6 +2131,8 @@ amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch,
(gdbarch, amd64_linux_remove_non_address_bits_watchpoint);
set_gdbarch_shadow_stack_push (gdbarch, amd64_linux_shadow_stack_push);
+ set_gdbarch_get_shadow_stack_pointer (gdbarch,
+ amd64_linux_get_shadow_stack_pointer);
dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
}
diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c
index 79f7e427841..6c54957ae75 100644
--- a/gdb/amd64-tdep.c
+++ b/gdb/amd64-tdep.c
@@ -1917,6 +1917,21 @@ amd64_displaced_step_fixup (struct gdbarch *gdbarch,
displaced_debug_printf ("relocated return addr at %s to %s",
paddress (gdbarch, rsp),
paddress (gdbarch, retaddr));
+
+ /* If shadow stack is enabled, we need to correct the return address
+ on the shadow stack too. */
+ bool shadow_stack_enabled;
+ std::optional<CORE_ADDR> ssp
+ = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
+ shadow_stack_enabled);
+ if (ssp.has_value () && shadow_stack_enabled)
+ {
+ write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
+ retaddr);
+ displaced_debug_printf ("relocated shadow stack return addr at %s "
+ "to %s", paddress (gdbarch, *ssp),
+ paddress (gdbarch, retaddr));
+ }
}
}
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index cf152bd1e6f..589fd50345f 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -27055,12 +27055,20 @@ the program stream must be an @code{ENDBR} instruction, otherwise the
processor signals a control protection exception.
@end itemize
-Impact on Call/Print:
+Impact on GDB commands:
+@itemize @bullet
+@item Call/Print:
Inferior calls in @value{GDBN} reset the current PC to the beginning of the
function that is called. No call instruction is executed, but the @code{RET}
instruction actually is. To avoid a control protection exception due to the
missing return address on the shadow stack, @value{GDBN} pushes the new return
address to the shadow stack and updates the shadow stack pointer.
+@item Step:
+With displaced stepping, @value{GDBN} may run an out of line copy of a call
+instruction. In this case, the wrong return address is pushed on the shadow
+stack. @value{GDBN} corrects this value to avoid a control protection
+exception. For more details on displaced stepping, see @ref{displaced-stepping}.
+@end itemize
@node Alpha
@subsection Alpha
@@ -41736,6 +41744,7 @@ GLOBAL Disassembler_2 (Matches current architecture)
@cindex out-of-line single-stepping
@item set displaced-stepping
@itemx show displaced-stepping
+@anchor{displaced-stepping}
Control whether or not @value{GDBN} will do @dfn{displaced stepping}
if the target supports it. Displaced stepping is a way to single-step
over breakpoints without removing them from the inferior, by executing
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index f3fa4e511e6..d83fdc0c85e 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -899,6 +899,21 @@ i386_displaced_step_fixup (struct gdbarch *gdbarch,
displaced_debug_printf ("relocated return addr at %s to %s",
paddress (gdbarch, esp),
paddress (gdbarch, retaddr));
+
+ /* If shadow stack is enabled, we need to correct the return address
+ on the shadow stack too. */
+ bool shadow_stack_enabled;
+ std::optional<CORE_ADDR> ssp
+ = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
+ shadow_stack_enabled);
+ if (ssp.has_value () && shadow_stack_enabled)
+ {
+ write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
+ retaddr);
+ displaced_debug_printf ("relocated shadow stack return addr at %s "
+ "to %s", paddress (gdbarch, *ssp),
+ paddress (gdbarch, retaddr));
+ }
}
}
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
new file mode 100644
index 00000000000..b5f168c2c42
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
@@ -0,0 +1,90 @@
+# Copyright 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 continue from call instructions with shadow stack and displaced
+# stepping being enabled.
+
+require allow_ssp_tests support_displaced_stepping
+
+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} \
+ additional_flags="-fcf-protection=return"] } {
+ return -1
+ }
+
+ # Enable displaced stepping.
+ gdb_test_no_output "set displaced-stepping on"
+ gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*"
+
+ if { ![runto_main] } {
+ return -1
+ }
+
+ # Get the address of the call1 instruction.
+ set call1_addr -1
+ gdb_test_multiple "disassemble main" "" {
+ -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call1>.*" {
+ set call1_addr $expect_out(1,string)
+ pass $gdb_test_name
+ }
+ }
+
+ if { $call1_addr == -1 } {
+ return -1
+ }
+
+ # Get the address of the call2 instruction.
+ set call2_addr -1
+ gdb_test_multiple "disassemble call1" "" {
+ -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call2>.*" {
+ set call2_addr $expect_out(1,string)
+ pass $gdb_test_name
+ }
+ }
+
+ if { $call2_addr == -1 } {
+ return -1
+ }
+
+ gdb_test "break *$call1_addr" \
+ "Breakpoint $decimal at $hex.*" \
+ "break at the address of the call1 instruction"
+
+ gdb_test "break *$call2_addr" \
+ "Breakpoint $decimal at $hex.*" \
+ "break at the address of the call2 instruction"
+
+ # We only resume until call1 instruction in case the first instruction
+ # we're stopped at is not yet the call1 instruction.
+ set stop_addr [get_valueof "/x" "\$pc" "" "value of pc after runto_main"]
+ if {[eval expr "$stop_addr < $call1_addr"]} {
+ gdb_test "continue" \
+ "Breakpoint $decimal, $call1_addr in main ().*" \
+ "continue until call1 instruction"
+ }
+ gdb_assert {$call1_addr == [get_valueof "/x" "\$pc" ""]}
+
+ # Test continue from breakpoint at call1 and call2 instructions.
+ gdb_test "continue" \
+ "Breakpoint $decimal, $call2_addr in call1 ().*" \
+ "continue from call1 instruction"
+
+ gdb_continue_to_end "continue from call2 instruction"
+}
--
2.34.1
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux.
2025-06-17 12:11 ` [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux Christina Schimpe
@ 2025-06-17 12:22 ` Eli Zaretskii
2025-06-17 15:16 ` Schimpe, Christina
2025-06-19 9:26 ` Luis Machado
1 sibling, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2025-06-17 12:22 UTC (permalink / raw)
To: Christina Schimpe; +Cc: gdb-patches, thiago.bauermann
> From: Christina Schimpe <christina.schimpe@intel.com>
> Cc: thiago.bauermann@linaro.org,
> eliz@gnu.org
> Date: Tue, 17 Jun 2025 12:11:47 +0000
>
> This patch enables displaced stepping to support Intel's Control-Flow
> Enforcement Technology (CET), which provides the shadow stack feature
> for the x86 architecture.
> Following the restriction of the linux kernel, enable displaced stepping
> for amd64 only.
>
> If displaced stepping is active and the single stepped instruction is a
> call instruction, the return address atop the stack is the address following
> the copied instruction. However, to allow normal program execution it has
> to be the address following the original instruction. Due to that reason,
> the return address is corrected in amd64_displaced_step_fixup and
> i386_displaced_step_fixup.
>
> To avoid a control-protection exception if shadow stack is active,
> the shadow stack top address must be corrected as well.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> gdb/NEWS | 3 +
> gdb/amd64-linux-tdep.c | 16 +++-
> gdb/amd64-tdep.c | 15 ++++
> gdb/doc/gdb.texinfo | 11 ++-
> gdb/i386-tdep.c | 15 ++++
> .../gdb.arch/amd64-shadow-stack-disp-step.exp | 90 +++++++++++++++++++
> 6 files changed, 147 insertions(+), 3 deletions(-)
> create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
The NEWS part is okay, thanks.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 53+ messages in thread
* RE: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux.
2025-06-17 12:22 ` Eli Zaretskii
@ 2025-06-17 15:16 ` Schimpe, Christina
0 siblings, 0 replies; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-17 15:16 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: gdb-patches, thiago.bauermann
Thanks for the quick feedback!
Christina
> -----Original Message-----
> From: Eli Zaretskii <eliz@gnu.org>
> Sent: Tuesday, June 17, 2025 2:22 PM
> To: Schimpe, Christina <christina.schimpe@intel.com>
> Cc: gdb-patches@sourceware.org; thiago.bauermann@linaro.org
> Subject: Re: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow
> stack on amd64 linux.
>
> > From: Christina Schimpe <christina.schimpe@intel.com>
> > Cc: thiago.bauermann@linaro.org,
> > eliz@gnu.org
> > Date: Tue, 17 Jun 2025 12:11:47 +0000
> >
> > This patch enables displaced stepping to support Intel's Control-Flow
> > Enforcement Technology (CET), which provides the shadow stack feature
> > for the x86 architecture.
> > Following the restriction of the linux kernel, enable displaced
> > stepping for amd64 only.
> >
> > If displaced stepping is active and the single stepped instruction is
> > a call instruction, the return address atop the stack is the address
> > following the copied instruction. However, to allow normal program
> > execution it has to be the address following the original instruction.
> > Due to that reason, the return address is corrected in
> > amd64_displaced_step_fixup and i386_displaced_step_fixup.
> >
> > To avoid a control-protection exception if shadow stack is active, the
> > shadow stack top address must be corrected as well.
> >
> > Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> > ---
> > gdb/NEWS | 3 +
> > gdb/amd64-linux-tdep.c | 16 +++-
> > gdb/amd64-tdep.c | 15 ++++
> > gdb/doc/gdb.texinfo | 11 ++-
> > gdb/i386-tdep.c | 15 ++++
> > .../gdb.arch/amd64-shadow-stack-disp-step.exp | 90
> > +++++++++++++++++++
> > 6 files changed, 147 insertions(+), 3 deletions(-) create mode
> > 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
>
> The NEWS part is okay, thanks.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
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
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux.
2025-06-17 12:11 ` [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux Christina Schimpe
2025-06-17 12:22 ` Eli Zaretskii
@ 2025-06-19 9:26 ` Luis Machado
2025-06-23 18:24 ` Schimpe, Christina
1 sibling, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-19 9:26 UTC (permalink / raw)
To: Christina Schimpe, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/17/25 13:11, Christina Schimpe wrote:
> This patch enables displaced stepping to support Intel's Control-Flow
> Enforcement Technology (CET), which provides the shadow stack feature
> for the x86 architecture.
> Following the restriction of the linux kernel, enable displaced stepping
> for amd64 only.
>
> If displaced stepping is active and the single stepped instruction is a
> call instruction, the return address atop the stack is the address following
> the copied instruction. However, to allow normal program execution it has
> to be the address following the original instruction. Due to that reason,
> the return address is corrected in amd64_displaced_step_fixup and
> i386_displaced_step_fixup.
I thought the description was slightly confusing. The above is the original
behavior, right?
>
> To avoid a control-protection exception if shadow stack is active,
> the shadow stack top address must be corrected as well.
And this one is the new behavior?
Might make sense to clarify it.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> gdb/NEWS | 3 +
> gdb/amd64-linux-tdep.c | 16 +++-
> gdb/amd64-tdep.c | 15 ++++
> gdb/doc/gdb.texinfo | 11 ++-
> gdb/i386-tdep.c | 15 ++++
> .../gdb.arch/amd64-shadow-stack-disp-step.exp | 90 +++++++++++++++++++
> 6 files changed, 147 insertions(+), 3 deletions(-)
> create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index b8fb7f0e484..15d2772230e 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,9 @@
>
> *** Changes since GDB 16
>
> +* Debugging Linux programs that use x86-64 or x86-64 with 32-bit pointer
> + size (X32) Shadow Stacks are now supported.
Should we mention CET somewhere?
> +
> * Support for the shadow stack pointer register on x86-64 or x86-64 with
> 32-bit pointer size (X32) GNU/Linux.
>
> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> index d847248659a..f989cfb3bf8 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -1935,8 +1935,10 @@ amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
> possible. */
>
> static std::optional<CORE_ADDR>
> -amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache)
> +amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
> + bool &shadow_stack_enabled)
> {
> + shadow_stack_enabled = false;
> const i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
>
> if (tdep == nullptr || tdep->ssp_regnum < 0)
> @@ -1954,6 +1956,9 @@ amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache)
> if (ssp == 0x0)
> return {};
>
> + /* In case there is a shadow stack pointer available which is non-null,
> + the shadow stack feature is enabled. */
> + shadow_stack_enabled = true;
> return ssp;
> }
>
> @@ -1964,8 +1969,13 @@ static void
> amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr,
> regcache *regcache)
> {
> + bool shadow_stack_enabled;
> std::optional<CORE_ADDR> ssp
> - = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
> + = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache,
> + shadow_stack_enabled);
> +
> + /* It's enough to check if SSP is valid as for amd64 linux shadow stack
> + is always enabled if SSP has a value. */
Is my understanding correct that for amd64's shadow stack support, whenever SSP
has a value, then shadow stack is enabled?
If so, maybe rephrase it as...
"For amd64/Linux, if SSP has a value that means shadow stack is enabled."
What do you think?
> if (!ssp.has_value ())
> return;
>
> @@ -2121,6 +2131,8 @@ amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch,
> (gdbarch, amd64_linux_remove_non_address_bits_watchpoint);
>
> set_gdbarch_shadow_stack_push (gdbarch, amd64_linux_shadow_stack_push);
> + set_gdbarch_get_shadow_stack_pointer (gdbarch,
> + amd64_linux_get_shadow_stack_pointer);
> dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
> }
>
> diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c
> index 79f7e427841..6c54957ae75 100644
> --- a/gdb/amd64-tdep.c
> +++ b/gdb/amd64-tdep.c
> @@ -1917,6 +1917,21 @@ amd64_displaced_step_fixup (struct gdbarch *gdbarch,
> displaced_debug_printf ("relocated return addr at %s to %s",
> paddress (gdbarch, rsp),
> paddress (gdbarch, retaddr));
> +
> + /* If shadow stack is enabled, we need to correct the return address
> + on the shadow stack too. */
> + bool shadow_stack_enabled;
> + std::optional<CORE_ADDR> ssp
> + = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
> + shadow_stack_enabled);
> + if (ssp.has_value () && shadow_stack_enabled)
> + {
> + write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
> + retaddr);
> + displaced_debug_printf ("relocated shadow stack return addr at %s "
> + "to %s", paddress (gdbarch, *ssp),
> + paddress (gdbarch, retaddr));
> + }
> }
> }
>
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index cf152bd1e6f..589fd50345f 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -27055,12 +27055,20 @@ the program stream must be an @code{ENDBR} instruction, otherwise the
> processor signals a control protection exception.
> @end itemize
>
> -Impact on Call/Print:
> +Impact on GDB commands:
> +@itemize @bullet
> +@item Call/Print:
> Inferior calls in @value{GDBN} reset the current PC to the beginning of the
> function that is called. No call instruction is executed, but the @code{RET}
> instruction actually is. To avoid a control protection exception due to the
> missing return address on the shadow stack, @value{GDBN} pushes the new return
> address to the shadow stack and updates the shadow stack pointer.
> +@item Step:
> +With displaced stepping, @value{GDBN} may run an out of line copy of a call
> +instruction. In this case, the wrong return address is pushed on the shadow
s/pushed on/pushed to
> +stack. @value{GDBN} corrects this value to avoid a control protection
> +exception. For more details on displaced stepping, see @ref{displaced-stepping}.
> +@end itemize
>
> @node Alpha
> @subsection Alpha
> @@ -41736,6 +41744,7 @@ GLOBAL Disassembler_2 (Matches current architecture)
> @cindex out-of-line single-stepping
> @item set displaced-stepping
> @itemx show displaced-stepping
> +@anchor{displaced-stepping}
> Control whether or not @value{GDBN} will do @dfn{displaced stepping}
> if the target supports it. Displaced stepping is a way to single-step
> over breakpoints without removing them from the inferior, by executing
> diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
> index f3fa4e511e6..d83fdc0c85e 100644
> --- a/gdb/i386-tdep.c
> +++ b/gdb/i386-tdep.c
> @@ -899,6 +899,21 @@ i386_displaced_step_fixup (struct gdbarch *gdbarch,
> displaced_debug_printf ("relocated return addr at %s to %s",
> paddress (gdbarch, esp),
> paddress (gdbarch, retaddr));
> +
> + /* If shadow stack is enabled, we need to correct the return address
> + on the shadow stack too. */
> + bool shadow_stack_enabled;
> + std::optional<CORE_ADDR> ssp
> + = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
> + shadow_stack_enabled);
> + if (ssp.has_value () && shadow_stack_enabled)
> + {
> + write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
> + retaddr);
> + displaced_debug_printf ("relocated shadow stack return addr at %s "
> + "to %s", paddress (gdbarch, *ssp),
> + paddress (gdbarch, retaddr));
> + }
> }
> }
>
> diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
> new file mode 100644
> index 00000000000..b5f168c2c42
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
> @@ -0,0 +1,90 @@
> +# Copyright 2024 Free Software Foundation, Inc.
s/2024/2025
> +
> +# 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 continue from call instructions with shadow stack and displaced
> +# stepping being enabled.
> +
> +require allow_ssp_tests support_displaced_stepping
> +
> +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} \
> + additional_flags="-fcf-protection=return"] } {
> + return -1
> + }
> +
> + # Enable displaced stepping.
> + gdb_test_no_output "set displaced-stepping on"
> + gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*"
> +
> + if { ![runto_main] } {
> + return -1
> + }
> +
> + # Get the address of the call1 instruction.
> + set call1_addr -1
> + gdb_test_multiple "disassemble main" "" {
> + -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call1>.*" {
> + set call1_addr $expect_out(1,string)
> + pass $gdb_test_name
> + }
> + }
> +
> + if { $call1_addr == -1 } {
> + return -1
> + }
> +
> + # Get the address of the call2 instruction.
> + set call2_addr -1
> + gdb_test_multiple "disassemble call1" "" {
> + -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call2>.*" {
> + set call2_addr $expect_out(1,string)
> + pass $gdb_test_name
> + }
> + }
> +
> + if { $call2_addr == -1 } {
> + return -1
> + }
> +
> + gdb_test "break *$call1_addr" \
> + "Breakpoint $decimal at $hex.*" \
> + "break at the address of the call1 instruction"
> +
> + gdb_test "break *$call2_addr" \
> + "Breakpoint $decimal at $hex.*" \
> + "break at the address of the call2 instruction"
> +
> + # We only resume until call1 instruction in case the first instruction
> + # we're stopped at is not yet the call1 instruction.
> + set stop_addr [get_valueof "/x" "\$pc" "" "value of pc after runto_main"]
> + if {[eval expr "$stop_addr < $call1_addr"]} {
> + gdb_test "continue" \
> + "Breakpoint $decimal, $call1_addr in main ().*" \
> + "continue until call1 instruction"
> + }
It was particularly clear why we need the check above. Is this due to how the compiler
might generate code and then we could risk stopping at the instruction we're interested
in when we "run to main"?
> + gdb_assert {$call1_addr == [get_valueof "/x" "\$pc" ""]}
> +
> + # Test continue from breakpoint at call1 and call2 instructions.
> + gdb_test "continue" \
> + "Breakpoint $decimal, $call2_addr in call1 ().*" \
> + "continue from call1 instruction"
> +
> + gdb_continue_to_end "continue from call2 instruction"
> +}
Reviewed-By: Luis Machado <luis.machado@arm.com>
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux.
2025-06-19 9:26 ` Luis Machado
@ 2025-06-23 18:24 ` Schimpe, Christina
2025-06-24 8:05 ` Luis Machado
0 siblings, 1 reply; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-23 18:24 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
Hi Luis,
Please find my comments below.
> -----Original Message-----
> From: Luis Machado <luis.machado@arm.com>
> Sent: Thursday, June 19, 2025 11:27 AM
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
> Subject: Re: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack
> on amd64 linux.
>
> On 6/17/25 13:11, Christina Schimpe wrote:
> > This patch enables displaced stepping to support Intel's Control-Flow
> > Enforcement Technology (CET), which provides the shadow stack feature
> > for the x86 architecture.
> > Following the restriction of the linux kernel, enable displaced
> > stepping for amd64 only.
> >
> > If displaced stepping is active and the single stepped instruction is
> > a call instruction, the return address atop the stack is the address
> > following the copied instruction. However, to allow normal program
> > execution it has to be the address following the original instruction.
> > Due to that reason, the return address is corrected in
> > amd64_displaced_step_fixup and i386_displaced_step_fixup.
>
> I thought the description was slightly confusing. The above is the original
> behavior, right?
Yes.
> >
> > To avoid a control-protection exception if shadow stack is active, the
> > shadow stack top address must be corrected as well.
>
> And this one is the new behavior?
>
> Might make sense to clarify it.
I agree, does this sound better?
[...], enable displaced stepping for amd64 only.
Currently, if displaced stepping is active and the single stepped instruction is
a call instruction, the return address atop the stack is the address following
the copied instruction. However, to allow normal program execution it has
to be the address following the original instruction. Due to that reason,
the return address is corrected in amd64_displaced_step_fixup and
i386_displaced_step_fixup.
For programs that are shadow-stack enabled we see a control-protection
exception, as the address on the shadow stack does not match the address
atop the stack.
Fix this by correcting the shadow stack top address as well.
>
> >
> > Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> > ---
> > gdb/NEWS | 3 +
> > gdb/amd64-linux-tdep.c | 16 +++-
> > gdb/amd64-tdep.c | 15 ++++
> > gdb/doc/gdb.texinfo | 11 ++-
> > gdb/i386-tdep.c | 15 ++++
> > .../gdb.arch/amd64-shadow-stack-disp-step.exp | 90
> > +++++++++++++++++++
> > 6 files changed, 147 insertions(+), 3 deletions(-) create mode
> > 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
> >
> > diff --git a/gdb/NEWS b/gdb/NEWS
> > index b8fb7f0e484..15d2772230e 100644
> > --- a/gdb/NEWS
> > +++ b/gdb/NEWS
> > @@ -3,6 +3,9 @@
> >
> > *** Changes since GDB 16
> >
> > +* Debugging Linux programs that use x86-64 or x86-64 with 32-bit
> > +pointer
> > + size (X32) Shadow Stacks are now supported.
>
> Should we mention CET somewhere?
Hm, in theory, this should work for AMD64 in general. But I don't know about
the state of AMD's shadow stack and am not sure how I could explain this in the
NEWS properly.
>
> > +
> > * Support for the shadow stack pointer register on x86-64 or x86-64 with
> > 32-bit pointer size (X32) GNU/Linux.
> >
> > diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
> > d847248659a..f989cfb3bf8 100644
> > --- a/gdb/amd64-linux-tdep.c
> > +++ b/gdb/amd64-linux-tdep.c
> > @@ -1935,8 +1935,10 @@
> amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
> > possible. */
> >
> > static std::optional<CORE_ADDR>
> > -amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache
> > *regcache)
> > +amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache
> *regcache,
> > + bool &shadow_stack_enabled)
> > {
> > + shadow_stack_enabled = false;
> > const i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep>
> > (gdbarch);
> >
> > if (tdep == nullptr || tdep->ssp_regnum < 0) @@ -1954,6 +1956,9 @@
> > amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache
> *regcache)
> > if (ssp == 0x0)
> > return {};
> >
> > + /* In case there is a shadow stack pointer available which is non-null,
> > + the shadow stack feature is enabled. */ shadow_stack_enabled =
> > + true;
> > return ssp;
> > }
> >
> > @@ -1964,8 +1969,13 @@ static void
> > amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR
> new_addr,
> > regcache *regcache)
> > {
> > + bool shadow_stack_enabled;
> > std::optional<CORE_ADDR> ssp
> > - = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
> > + = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache,
> > + shadow_stack_enabled);
> > +
> > + /* It's enough to check if SSP is valid as for amd64 linux shadow stack
> > + is always enabled if SSP has a value. */
>
> Is my understanding correct that for amd64's shadow stack support, whenever
> SSP has a value, then shadow stack is enabled?
Yes, exactly.
>
> If so, maybe rephrase it as...
>
> "For amd64/Linux, if SSP has a value that means shadow stack is enabled."
>
> What do you think?
Yes, this makes it clearer. Thank you.
> > if (!ssp.has_value ())
> > return;
> >
> > @@ -2121,6 +2131,8 @@ amd64_linux_init_abi_common(struct
> gdbarch_info info, struct gdbarch *gdbarch,
> > (gdbarch, amd64_linux_remove_non_address_bits_watchpoint);
> >
> > set_gdbarch_shadow_stack_push (gdbarch,
> > amd64_linux_shadow_stack_push);
> > + set_gdbarch_get_shadow_stack_pointer (gdbarch,
> > +
> amd64_linux_get_shadow_stack_pointer);
> > dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg); }
> >
> > diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c index
> > 79f7e427841..6c54957ae75 100644
> > --- a/gdb/amd64-tdep.c
> > +++ b/gdb/amd64-tdep.c
> > @@ -1917,6 +1917,21 @@ amd64_displaced_step_fixup (struct gdbarch
> *gdbarch,
> > displaced_debug_printf ("relocated return addr at %s to %s",
> > paddress (gdbarch, rsp),
> > paddress (gdbarch, retaddr));
> > +
> > + /* If shadow stack is enabled, we need to correct the return address
> > + on the shadow stack too. */
> > + bool shadow_stack_enabled;
> > + std::optional<CORE_ADDR> ssp
> > + = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
> > + shadow_stack_enabled);
> > + if (ssp.has_value () && shadow_stack_enabled)
> > + {
> > + write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
> > + retaddr);
> > + displaced_debug_printf ("relocated shadow stack return addr at %s "
> > + "to %s", paddress (gdbarch, *ssp),
> > + paddress (gdbarch, retaddr));
> > + }
> > }
> > }
> >
> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
> > cf152bd1e6f..589fd50345f 100644
> > --- a/gdb/doc/gdb.texinfo
> > +++ b/gdb/doc/gdb.texinfo
> > @@ -27055,12 +27055,20 @@ the program stream must be an
> @code{ENDBR}
> > instruction, otherwise the processor signals a control protection exception.
> > @end itemize
> >
> > -Impact on Call/Print:
> > +Impact on GDB commands:
> > +@itemize @bullet
> > +@item Call/Print:
> > Inferior calls in @value{GDBN} reset the current PC to the beginning
> > of the function that is called. No call instruction is executed, but
> > the @code{RET} instruction actually is. To avoid a control
> > protection exception due to the missing return address on the shadow
> > stack, @value{GDBN} pushes the new return address to the shadow stack and
> updates the shadow stack pointer.
> > +@item Step:
> > +With displaced stepping, @value{GDBN} may run an out of line copy of
> > +a call instruction. In this case, the wrong return address is pushed
> > +on the shadow
>
> s/pushed on/pushed to
Will fix.
>
> > +stack. @value{GDBN} corrects this value to avoid a control
> > +protection exception. For more details on displaced stepping, see
> @ref{displaced-stepping}.
> > +@end itemize
> >
> > @node Alpha
> > @subsection Alpha
> > @@ -41736,6 +41744,7 @@ GLOBAL Disassembler_2 (Matches
> current architecture)
> > @cindex out-of-line single-stepping
> > @item set displaced-stepping
> > @itemx show displaced-stepping
> > +@anchor{displaced-stepping}
> > Control whether or not @value{GDBN} will do @dfn{displaced stepping}
> > if the target supports it. Displaced stepping is a way to single-step
> > over breakpoints without removing them from the inferior, by executing
> > diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c index
> > f3fa4e511e6..d83fdc0c85e 100644
> > --- a/gdb/i386-tdep.c
> > +++ b/gdb/i386-tdep.c
> > @@ -899,6 +899,21 @@ i386_displaced_step_fixup (struct gdbarch *gdbarch,
> > displaced_debug_printf ("relocated return addr at %s to %s",
> > paddress (gdbarch, esp),
> > paddress (gdbarch, retaddr));
> > +
> > + /* If shadow stack is enabled, we need to correct the return address
> > + on the shadow stack too. */
> > + bool shadow_stack_enabled;
> > + std::optional<CORE_ADDR> ssp
> > + = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
> > + shadow_stack_enabled);
> > + if (ssp.has_value () && shadow_stack_enabled)
> > + {
> > + write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
> > + retaddr);
> > + displaced_debug_printf ("relocated shadow stack return addr at %s "
> > + "to %s", paddress (gdbarch, *ssp),
> > + paddress (gdbarch, retaddr));
> > + }
> > }
> > }
> >
> > diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
> > b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
> > new file mode 100644
> > index 00000000000..b5f168c2c42
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
> > @@ -0,0 +1,90 @@
> > +# Copyright 2024 Free Software Foundation, Inc.
>
> s/2024/2025
Will fix.
> > +
> > +# 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 continue from call instructions with shadow stack and
> > +displaced # stepping being enabled.
> > +
> > +require allow_ssp_tests support_displaced_stepping
> > +
> > +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} \
> > + additional_flags="-fcf-protection=return"] } {
> > + return -1
> > + }
> > +
> > + # Enable displaced stepping.
> > + gdb_test_no_output "set displaced-stepping on"
> > + gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*"
> > +
> > + if { ![runto_main] } {
> > + return -1
> > + }
> > +
> > + # Get the address of the call1 instruction.
> > + set call1_addr -1
> > + gdb_test_multiple "disassemble main" "" {
> > + -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call1>.*" {
> > + set call1_addr $expect_out(1,string)
> > + pass $gdb_test_name
> > + }
> > + }
> > +
> > + if { $call1_addr == -1 } {
> > + return -1
> > + }
> > +
> > + # Get the address of the call2 instruction.
> > + set call2_addr -1
> > + gdb_test_multiple "disassemble call1" "" {
> > + -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call2>.*" {
> > + set call2_addr $expect_out(1,string)
> > + pass $gdb_test_name
> > + }
> > + }
> > +
> > + if { $call2_addr == -1 } {
> > + return -1
> > + }
> > +
> > + gdb_test "break *$call1_addr" \
> > + "Breakpoint $decimal at $hex.*" \
> > + "break at the address of the call1 instruction"
> > +
> > + gdb_test "break *$call2_addr" \
> > + "Breakpoint $decimal at $hex.*" \
> > + "break at the address of the call2 instruction"
> > +
> > + # We only resume until call1 instruction in case the first instruction
> > + # we're stopped at is not yet the call1 instruction.
> > + set stop_addr [get_valueof "/x" "\$pc" "" "value of pc after runto_main"]
> > + if {[eval expr "$stop_addr < $call1_addr"]} {
> > + gdb_test "continue" \
> > + "Breakpoint $decimal, $call1_addr in main ().*" \
> > + "continue until call1 instruction"
> > + }
>
> It was particularly clear why we need the check above. Is this due to how the
> compiler might generate code and then we could risk stopping at the
> instruction we're interested in when we "run to main"?
I assume you mean "It was not clear" in
"It was particularly clear why we need the check above." :
Yes, this is related to compiler behaviour.
With gcc 15 the assembler changed a bit and with "run to main" we stopped already
at the call instruction.
I agree, the comment might be confusing. Would such a comment be better?
# For gcc 15 we already stop at the call instruction when we "run to "main".
# Only resume in case the first instruction we're stopped at is not yet the call
# instruction.
>
> > + gdb_assert {$call1_addr == [get_valueof "/x" "\$pc" ""]}
> > +
> > + # Test continue from breakpoint at call1 and call2 instructions.
> > + gdb_test "continue" \
> > + "Breakpoint $decimal, $call2_addr in call1 ().*" \
> > + "continue from call1 instruction"
> > +
> > + gdb_continue_to_end "continue from call2 instruction"
> > +}
>
> Reviewed-By: Luis Machado <luis.machado@arm.com>
Thanks for all the review efforts!
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
^ permalink raw reply [flat|nested] 53+ messages in thread* Re: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux.
2025-06-23 18:24 ` Schimpe, Christina
@ 2025-06-24 8:05 ` Luis Machado
2025-06-27 19:26 ` Schimpe, Christina
0 siblings, 1 reply; 53+ messages in thread
From: Luis Machado @ 2025-06-24 8:05 UTC (permalink / raw)
To: Schimpe, Christina, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/23/25 19:24, Schimpe, Christina wrote:
> Hi Luis,
>
> Please find my comments below.
>
>> -----Original Message-----
>> From: Luis Machado <luis.machado@arm.com>
>> Sent: Thursday, June 19, 2025 11:27 AM
>> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>> patches@sourceware.org
>> Cc: thiago.bauermann@linaro.org; eliz@gnu.org
>> Subject: Re: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack
>> on amd64 linux.
>>
>> On 6/17/25 13:11, Christina Schimpe wrote:
>>> This patch enables displaced stepping to support Intel's Control-Flow
>>> Enforcement Technology (CET), which provides the shadow stack feature
>>> for the x86 architecture.
>>> Following the restriction of the linux kernel, enable displaced
>>> stepping for amd64 only.
>>>
>>> If displaced stepping is active and the single stepped instruction is
>>> a call instruction, the return address atop the stack is the address
>>> following the copied instruction. However, to allow normal program
>>> execution it has to be the address following the original instruction.
>>> Due to that reason, the return address is corrected in
>>> amd64_displaced_step_fixup and i386_displaced_step_fixup.
>>
>> I thought the description was slightly confusing. The above is the original
>> behavior, right?
>
> Yes.
>
>>>
>>> To avoid a control-protection exception if shadow stack is active, the
>>> shadow stack top address must be corrected as well.
>>
>> And this one is the new behavior?
>>
>> Might make sense to clarify it.
>
> I agree, does this sound better?
>
> [...], enable displaced stepping for amd64 only.
>
> Currently, if displaced stepping is active and the single stepped instruction is
> a call instruction, the return address atop the stack is the address following
> the copied instruction. However, to allow normal program execution it has
> to be the address following the original instruction. Due to that reason,
> the return address is corrected in amd64_displaced_step_fixup and
> i386_displaced_step_fixup.
>
> For programs that are shadow-stack enabled we see a control-protection
> exception, as the address on the shadow stack does not match the address
> atop the stack.
>
> Fix this by correcting the shadow stack top address as well.
>
That makes it better. Thanks!
>>
>>>
>>> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
>>> ---
>>> gdb/NEWS | 3 +
>>> gdb/amd64-linux-tdep.c | 16 +++-
>>> gdb/amd64-tdep.c | 15 ++++
>>> gdb/doc/gdb.texinfo | 11 ++-
>>> gdb/i386-tdep.c | 15 ++++
>>> .../gdb.arch/amd64-shadow-stack-disp-step.exp | 90
>>> +++++++++++++++++++
>>> 6 files changed, 147 insertions(+), 3 deletions(-) create mode
>>> 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
>>>
>>> diff --git a/gdb/NEWS b/gdb/NEWS
>>> index b8fb7f0e484..15d2772230e 100644
>>> --- a/gdb/NEWS
>>> +++ b/gdb/NEWS
>>> @@ -3,6 +3,9 @@
>>>
>>> *** Changes since GDB 16
>>>
>>> +* Debugging Linux programs that use x86-64 or x86-64 with 32-bit
>>> +pointer
>>> + size (X32) Shadow Stacks are now supported.
>>
>> Should we mention CET somewhere?
>
> Hm, in theory, this should work for AMD64 in general. But I don't know about
> the state of AMD's shadow stack and am not sure how I could explain this in the
> NEWS properly.
>
Alright. This is OK then.
>>
>>> +
>>> * Support for the shadow stack pointer register on x86-64 or x86-64 with
>>> 32-bit pointer size (X32) GNU/Linux.
>>>
>>> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index
>>> d847248659a..f989cfb3bf8 100644
>>> --- a/gdb/amd64-linux-tdep.c
>>> +++ b/gdb/amd64-linux-tdep.c
>>> @@ -1935,8 +1935,10 @@
>> amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
>>> possible. */
>>>
>>> static std::optional<CORE_ADDR>
>>> -amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache
>>> *regcache)
>>> +amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache
>> *regcache,
>>> + bool &shadow_stack_enabled)
>>> {
>>> + shadow_stack_enabled = false;
>>> const i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep>
>>> (gdbarch);
>>>
>>> if (tdep == nullptr || tdep->ssp_regnum < 0) @@ -1954,6 +1956,9 @@
>>> amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache
>> *regcache)
>>> if (ssp == 0x0)
>>> return {};
>>>
>>> + /* In case there is a shadow stack pointer available which is non-null,
>>> + the shadow stack feature is enabled. */ shadow_stack_enabled =
>>> + true;
>>> return ssp;
>>> }
>>>
>>> @@ -1964,8 +1969,13 @@ static void
>>> amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR
>> new_addr,
>>> regcache *regcache)
>>> {
>>> + bool shadow_stack_enabled;
>>> std::optional<CORE_ADDR> ssp
>>> - = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
>>> + = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache,
>>> + shadow_stack_enabled);
>>> +
>>> + /* It's enough to check if SSP is valid as for amd64 linux shadow stack
>>> + is always enabled if SSP has a value. */
>>
>> Is my understanding correct that for amd64's shadow stack support, whenever
>> SSP has a value, then shadow stack is enabled?
>
> Yes, exactly.
>
>>
>> If so, maybe rephrase it as...
>>
>> "For amd64/Linux, if SSP has a value that means shadow stack is enabled."
>>
>> What do you think?
>
> Yes, this makes it clearer. Thank you.
>
>>> if (!ssp.has_value ())
>>> return;
>>>
>>> @@ -2121,6 +2131,8 @@ amd64_linux_init_abi_common(struct
>> gdbarch_info info, struct gdbarch *gdbarch,
>>> (gdbarch, amd64_linux_remove_non_address_bits_watchpoint);
>>>
>>> set_gdbarch_shadow_stack_push (gdbarch,
>>> amd64_linux_shadow_stack_push);
>>> + set_gdbarch_get_shadow_stack_pointer (gdbarch,
>>> +
>> amd64_linux_get_shadow_stack_pointer);
>>> dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg); }
>>>
>>> diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c index
>>> 79f7e427841..6c54957ae75 100644
>>> --- a/gdb/amd64-tdep.c
>>> +++ b/gdb/amd64-tdep.c
>>> @@ -1917,6 +1917,21 @@ amd64_displaced_step_fixup (struct gdbarch
>> *gdbarch,
>>> displaced_debug_printf ("relocated return addr at %s to %s",
>>> paddress (gdbarch, rsp),
>>> paddress (gdbarch, retaddr));
>>> +
>>> + /* If shadow stack is enabled, we need to correct the return address
>>> + on the shadow stack too. */
>>> + bool shadow_stack_enabled;
>>> + std::optional<CORE_ADDR> ssp
>>> + = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
>>> + shadow_stack_enabled);
>>> + if (ssp.has_value () && shadow_stack_enabled)
>>> + {
>>> + write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
>>> + retaddr);
>>> + displaced_debug_printf ("relocated shadow stack return addr at %s "
>>> + "to %s", paddress (gdbarch, *ssp),
>>> + paddress (gdbarch, retaddr));
>>> + }
>>> }
>>> }
>>>
>>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
>>> cf152bd1e6f..589fd50345f 100644
>>> --- a/gdb/doc/gdb.texinfo
>>> +++ b/gdb/doc/gdb.texinfo
>>> @@ -27055,12 +27055,20 @@ the program stream must be an
>> @code{ENDBR}
>>> instruction, otherwise the processor signals a control protection exception.
>>> @end itemize
>>>
>>> -Impact on Call/Print:
>>> +Impact on GDB commands:
>>> +@itemize @bullet
>>> +@item Call/Print:
>>> Inferior calls in @value{GDBN} reset the current PC to the beginning
>>> of the function that is called. No call instruction is executed, but
>>> the @code{RET} instruction actually is. To avoid a control
>>> protection exception due to the missing return address on the shadow
>>> stack, @value{GDBN} pushes the new return address to the shadow stack and
>> updates the shadow stack pointer.
>>> +@item Step:
>>> +With displaced stepping, @value{GDBN} may run an out of line copy of
>>> +a call instruction. In this case, the wrong return address is pushed
>>> +on the shadow
>>
>> s/pushed on/pushed to
>
> Will fix.
>
>>
>>> +stack. @value{GDBN} corrects this value to avoid a control
>>> +protection exception. For more details on displaced stepping, see
>> @ref{displaced-stepping}.
>>> +@end itemize
>>>
>>> @node Alpha
>>> @subsection Alpha
>>> @@ -41736,6 +41744,7 @@ GLOBAL Disassembler_2 (Matches
>> current architecture)
>>> @cindex out-of-line single-stepping
>>> @item set displaced-stepping
>>> @itemx show displaced-stepping
>>> +@anchor{displaced-stepping}
>>> Control whether or not @value{GDBN} will do @dfn{displaced stepping}
>>> if the target supports it. Displaced stepping is a way to single-step
>>> over breakpoints without removing them from the inferior, by executing
>>> diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c index
>>> f3fa4e511e6..d83fdc0c85e 100644
>>> --- a/gdb/i386-tdep.c
>>> +++ b/gdb/i386-tdep.c
>>> @@ -899,6 +899,21 @@ i386_displaced_step_fixup (struct gdbarch *gdbarch,
>>> displaced_debug_printf ("relocated return addr at %s to %s",
>>> paddress (gdbarch, esp),
>>> paddress (gdbarch, retaddr));
>>> +
>>> + /* If shadow stack is enabled, we need to correct the return address
>>> + on the shadow stack too. */
>>> + bool shadow_stack_enabled;
>>> + std::optional<CORE_ADDR> ssp
>>> + = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
>>> + shadow_stack_enabled);
>>> + if (ssp.has_value () && shadow_stack_enabled)
>>> + {
>>> + write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
>>> + retaddr);
>>> + displaced_debug_printf ("relocated shadow stack return addr at %s "
>>> + "to %s", paddress (gdbarch, *ssp),
>>> + paddress (gdbarch, retaddr));
>>> + }
>>> }
>>> }
>>>
>>> diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
>>> b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
>>> new file mode 100644
>>> index 00000000000..b5f168c2c42
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
>>> @@ -0,0 +1,90 @@
>>> +# Copyright 2024 Free Software Foundation, Inc.
>>
>> s/2024/2025
>
> Will fix.
>
>>> +
>>> +# 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 continue from call instructions with shadow stack and
>>> +displaced # stepping being enabled.
>>> +
>>> +require allow_ssp_tests support_displaced_stepping
>>> +
>>> +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} \
>>> + additional_flags="-fcf-protection=return"] } {
>>> + return -1
>>> + }
>>> +
>>> + # Enable displaced stepping.
>>> + gdb_test_no_output "set displaced-stepping on"
>>> + gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*"
>>> +
>>> + if { ![runto_main] } {
>>> + return -1
>>> + }
>>> +
>>> + # Get the address of the call1 instruction.
>>> + set call1_addr -1
>>> + gdb_test_multiple "disassemble main" "" {
>>> + -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call1>.*" {
>>> + set call1_addr $expect_out(1,string)
>>> + pass $gdb_test_name
>>> + }
>>> + }
>>> +
>>> + if { $call1_addr == -1 } {
>>> + return -1
>>> + }
>>> +
>>> + # Get the address of the call2 instruction.
>>> + set call2_addr -1
>>> + gdb_test_multiple "disassemble call1" "" {
>>> + -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call2>.*" {
>>> + set call2_addr $expect_out(1,string)
>>> + pass $gdb_test_name
>>> + }
>>> + }
>>> +
>>> + if { $call2_addr == -1 } {
>>> + return -1
>>> + }
>>> +
>>> + gdb_test "break *$call1_addr" \
>>> + "Breakpoint $decimal at $hex.*" \
>>> + "break at the address of the call1 instruction"
>>> +
>>> + gdb_test "break *$call2_addr" \
>>> + "Breakpoint $decimal at $hex.*" \
>>> + "break at the address of the call2 instruction"
>>> +
>>> + # We only resume until call1 instruction in case the first instruction
>>> + # we're stopped at is not yet the call1 instruction.
>>> + set stop_addr [get_valueof "/x" "\$pc" "" "value of pc after runto_main"]
>>> + if {[eval expr "$stop_addr < $call1_addr"]} {
>>> + gdb_test "continue" \
>>> + "Breakpoint $decimal, $call1_addr in main ().*" \
>>> + "continue until call1 instruction"
>>> + }
>>
>> It was particularly clear why we need the check above. Is this due to how the
>> compiler might generate code and then we could risk stopping at the
>> instruction we're interested in when we "run to main"?
>
> I assume you mean "It was not clear" in
> "It was particularly clear why we need the check above." :
Yes, sorry. I messed that one up.
>
> Yes, this is related to compiler behaviour.
> With gcc 15 the assembler changed a bit and with "run to main" we stopped already
> at the call instruction.
>
> I agree, the comment might be confusing. Would such a comment be better?
>
> # For gcc 15 we already stop at the call instruction when we "run to "main".
> # Only resume in case the first instruction we're stopped at is not yet the call
> # instruction.
>
I think it is enough to mention that depending on instruction generation we might end up
in the call instruction after "runto_main". Then we don't make any references to versions,
since they change in behavior sometimes.
>>
>>> + gdb_assert {$call1_addr == [get_valueof "/x" "\$pc" ""]}
>>> +
>>> + # Test continue from breakpoint at call1 and call2 instructions.
>>> + gdb_test "continue" \
>>> + "Breakpoint $decimal, $call2_addr in call1 ().*" \
>>> + "continue from call1 instruction"
>>> +
>>> + gdb_continue_to_end "continue from call2 instruction"
>>> +}
>>
>> Reviewed-By: Luis Machado <luis.machado@arm.com>
>
> Thanks for all the review efforts!
> 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
^ permalink raw reply [flat|nested] 53+ messages in thread* RE: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux.
2025-06-24 8:05 ` Luis Machado
@ 2025-06-27 19:26 ` Schimpe, Christina
2025-06-28 10:35 ` Luis Machado
0 siblings, 1 reply; 53+ messages in thread
From: Schimpe, Christina @ 2025-06-27 19:26 UTC (permalink / raw)
To: Luis Machado, gdb-patches; +Cc: thiago.bauermann, eliz
Hi Luis,
Thanks a lot for the quick feedback.
<...>
> >
> > Yes, this is related to compiler behaviour.
> > With gcc 15 the assembler changed a bit and with "run to main" we
> > stopped already at the call instruction.
> >
> > I agree, the comment might be confusing. Would such a comment be
> better?
> >
> > # For gcc 15 we already stop at the call instruction when we "run to "main".
> > # Only resume in case the first instruction we're stopped at is not
> > yet the call # instruction.
> >
>
> I think it is enough to mention that depending on instruction generation we
> might end up in the call instruction after "runto_main". Then we don't make
> any references to versions, since they change in behavior sometimes.
Alright, so instead of
'For gcc 15 we already stop at the call instruction when we "runto_main".'
I'll write
'Depending on instruction generation we might end up in the call instruction after "runto_main".'
<...>
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
^ permalink raw reply [flat|nested] 53+ messages in thread
* Re: [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux.
2025-06-27 19:26 ` Schimpe, Christina
@ 2025-06-28 10:35 ` Luis Machado
0 siblings, 0 replies; 53+ messages in thread
From: Luis Machado @ 2025-06-28 10:35 UTC (permalink / raw)
To: Schimpe, Christina, gdb-patches; +Cc: thiago.bauermann, eliz
On 6/27/25 20:26, Schimpe, Christina wrote:
> Hi Luis,
>
> Thanks a lot for the quick feedback.
>
> <...>
>>>
>>> Yes, this is related to compiler behaviour.
>>> With gcc 15 the assembler changed a bit and with "run to main" we
>>> stopped already at the call instruction.
>>>
>>> I agree, the comment might be confusing. Would such a comment be
>> better?
>>>
>>> # For gcc 15 we already stop at the call instruction when we "run to "main".
>>> # Only resume in case the first instruction we're stopped at is not
>>> yet the call # instruction.
>>>
>>
>> I think it is enough to mention that depending on instruction generation we
>> might end up in the call instruction after "runto_main". Then we don't make
>> any references to versions, since they change in behavior sometimes.
>
> Alright, so instead of
>
> 'For gcc 15 we already stop at the call instruction when we "runto_main".'
>
> I'll write
>
> 'Depending on instruction generation we might end up in the call instruction after "runto_main".'
Sounds good to me. Thanks.
^ permalink raw reply [flat|nested] 53+ messages in thread