Message ID | 20250608010338.2234530-7-thiago.bauermann@linaro.org |
---|---|
State | New |
Headers | show |
Series | AArch64 Guarded Control Stack support | expand |
Hi Thiago, I have one general comment for the 3 patches which add the tests for GCS: Shouldn't they better be part of the patch which introduces the feature in GDB, instead of separate commits for testing only? Christina > -----Original Message----- > From: Thiago Jung Bauermann <thiago.bauermann@linaro.org> > Sent: Sunday, June 8, 2025 3:03 AM > To: gdb-patches@sourceware.org > Subject: [PATCH 6/8] GDB: testsuite: Add gdb.arch/aarch64-gcs.exp testcase > > Also add allow_aarch64_gcs_tests procedure to determine whether Guarded > Control Stack tests should run. > --- > gdb/testsuite/gdb.arch/aarch64-gcs.c | 168 +++++++++++++++++++++++++ > gdb/testsuite/gdb.arch/aarch64-gcs.exp | 78 ++++++++++++ > gdb/testsuite/lib/gdb.exp | 58 +++++++++ > 3 files changed, 304 insertions(+) > create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs.c > create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs.exp > > diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.c > b/gdb/testsuite/gdb.arch/aarch64-gcs.c > new file mode 100644 > index 000000000000..8e579de10cdb > --- /dev/null > +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.c > @@ -0,0 +1,168 @@ > +/* This test program is part of GDB, the GNU debugger. > + > + Copyright 2025 Free Software Foundation, Inc. > + > + This program is free software; you can redistribute it and/or modify > + it under the terms of the GNU General Public License as published by > + the Free Software Foundation; either version 3 of the License, or > + (at your option) any later version. > + > + This program is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + GNU General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with this program. If not, see > + <http://www.gnu.org/licenses/>. */ > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <signal.h> > +#include <sys/auxv.h> > +#include <sys/syscall.h> > +#include <linux/prctl.h> > + > +/* Feature check for Guarded Control Stack. */ #ifndef HWCAP_GCS > +#define HWCAP_GCS (1UL << 32) #endif > + > +#ifndef PR_GET_SHADOW_STACK_STATUS > +#define PR_GET_SHADOW_STACK_STATUS 74 > +#define PR_SET_SHADOW_STACK_STATUS 75 > +#define PR_SHADOW_STACK_ENABLE (1UL << 0) #endif > + > +/* We need to use a macro to call prctl because after GCS is enabled, it's not > + possible to return from the function which enabled it. This is because the > + return address of the calling function isn't on the GCS. */ > +#define my_syscall2(num, arg1, arg2) \ > + ({ \ > + register long _num __asm__("x8") = (num); \ > + register long _arg1 __asm__("x0") = (long)(arg1); \ > + register long _arg2 __asm__("x1") = (long)(arg2); \ > + register long _arg3 __asm__("x2") = 0; \ > + register long _arg4 __asm__("x3") = 0; \ > + register long _arg5 __asm__("x4") = 0; \ > + \ > + __asm__ volatile("svc #0\n" \ > + : "=r"(_arg1) \ > + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ > + "r"(_arg5), "r"(_num) \ > + : "memory", "cc"); \ > + _arg1; \ > + }) > + > +#define get_gcspr(void) \ > + ({ \ > + unsigned long *gcspr; \ > + \ > + /* Get GCSPR_EL0. */ \ > + asm volatile("mrs %0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc"); \ > + \ > + gcspr; \ > + }) > + > +static unsigned long *handler_gcspr = 0; > + > +static void > +handler (int sig) > +{ > + handler_gcspr = get_gcspr (); > +} > + > +static int __attribute__ ((unused)) > +called_from_gdb (int val) > +{ > + return val + 1; > +} > + > +/* Corrupt the return address to see if GDB will report a SIGSEGV with the > expected > + $_siginfo.si_code. */ > +static void __attribute__ ((noinline)) > +normal_function2 (void) > +{ > + /* x30 holds the return address. */ > + register unsigned long x30 __asm__("x30") __attribute__ ((unused)); > + > + /* Cause a GCS exception. */ > + x30 = 0xbadc0ffee; > + __asm__ volatile("ret\n"); > +} > + > +static inline void __attribute__ ((__always_inline__)) > +inline_function2 (void) > +{ > + normal_function2 (); > +} > + > +/* Corrupt the return address to see if GDB will report a GCS error in this > + function's frame . */ > +static void __attribute__ ((noinline)) > +normal_function1 (void) > +{ > + /* x30 holds the return address. */ > + register unsigned long x30 __asm__ ("x30") __attribute__ ((unused)); > + x30 = 0xbadc0ffee; > + inline_function2 (); > +} > + > +static inline void __attribute__ ((__always_inline__)) > +inline_function1 (void) > +{ > + normal_function1 (); > +} > + > +int > +main (void) > +{ > + if (!(getauxval (AT_HWCAP) & HWCAP_GCS)) > + { > + fprintf (stderr, "GCS support not found in AT_HWCAP\n"); > + return EXIT_FAILURE; > + } > + > + /* Force shadow stacks on, our tests *should* be fine with or > + without libc support and with or without this having ended > + up tagged for GCS and enabled by the dynamic linker. We > + can't use the libc prctl() function since we can't return > + from enabling the stack. Also lock GCS if not already > + locked so we can test behaviour when it's locked. */ unsigned > + long gcs_mode; int ret = my_syscall2 (__NR_prctl, > + PR_GET_SHADOW_STACK_STATUS, &gcs_mode); if (ret) > + { > + fprintf (stderr, "Failed to read GCS state: %d\n", ret); > + return EXIT_FAILURE; > + } > + > + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) > + { > + gcs_mode = PR_SHADOW_STACK_ENABLE; > + ret = my_syscall2 (__NR_prctl, PR_SET_SHADOW_STACK_STATUS, > gcs_mode); > + if (ret) > + { > + fprintf (stderr, "Failed to configure GCS: %d\n", ret); > + return EXIT_FAILURE; > + } > + } > + > + /* This is used by GDB. */ > + __attribute__((unused)) unsigned long *gcspr = get_gcspr (); > + > + struct sigaction act = { 0 }; > + > + act.sa_handler = &handler; /* Break here. */ > + if (sigaction (SIGUSR1, &act, NULL) == -1) > + { > + perror ("sigaction"); > + exit (EXIT_FAILURE); > + } > + > + raise (SIGUSR1); > + > + inline_function1 (); > + > + /* Avoid returning, in case libc doesn't understand GCS. */ > + exit (EXIT_SUCCESS); > +} > diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.exp > b/gdb/testsuite/gdb.arch/aarch64-gcs.exp > new file mode 100644 > index 000000000000..211fabf2f86b > --- /dev/null > +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.exp > @@ -0,0 +1,78 @@ > +# Copyright 2025 Free Software Foundation, Inc. > + > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 3 of the License, or # > +(at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, # but > +WITHOUT ANY WARRANTY; without even the implied warranty of # > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU > +General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License # > +along with this program. If not, see <http://www.gnu.org/licenses/>. > + > +# Test a binary that uses a Guarded Control Stack. > + > +require allow_aarch64_gcs_tests > + > +standard_testfile > + > +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { > + return > +} > + > +set linespec ${srcfile}:[gdb_get_line_number "Break here"] > + > +if ![runto ${linespec}] { > + return > +} > + > +gdb_test "print \$gcs_features_enabled" \ > + [string_to_regexp { = [ PR_SHADOW_STACK_ENABLE ]}] \ > + "GCS is enabled" > + > +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "GDB knows about gcspr" > +gdb_test "print \$gcspr == gcspr" ". = 1" "GDB has the correct gcspr value" > +gdb_test_no_output "set \$gcspr_in_main = \$gcspr" \ > + "save gcspr value in main for later" > + > +# If the inferior function call fails, we don't want the tests > +following it # to be affected. > +gdb_test_no_output "set unwindonsignal on" > +gdb_test "print called_from_gdb (41)" ". = 42" "call inferior function" > + > +gdb_test "break handler" "Breakpoint \[0-9\]+ .*aarch64-gcs.c, line \[0-9\]+\\." > +gdb_test "handle SIGUSR1 nostop" \ > + ".*\r\nSIGUSR1\\s+No\\s+Yes\\s+Yes\\s+User defined signal 1" \ > + "let the inferior receive SIGUSR1 uninterrupted" > +gdb_test "continue" \ > + ".*\r\nBreakpoint \[0-9\]+, handler \\(sig=10\\) at .*aarch64- > gcs.c.*handler_gcspr = get_gcspr \\(\\);" \ > + "continue to signal handler" > + > +gdb_test_no_output "set \$gcspr_in_handler = \$gcspr" \ > + "save gcspr value in handler for later" > +# Select the frame above the <signal handler called> frame, which makes > +GDB # unwind the gcspr from the signal frame GCS context. > +gdb_test "frame 2" "#2 ($hex in )?\\S+ \\(.*\\) (at|from) \\S+.*" \ > + "reached frame 2" > +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "gcspr in frame level 2" > +gdb_test "print \$gcspr == \$gcspr_in_handler + 8" ". = 1" \ > + "gcspr unwound from signal context is correct" > + > +gdb_test "continue" \ > + [multi_line \ > + "Continuing\\." \ > + "" \ > + "Program received signal SIGSEGV, Segmentation fault" \ > + "Guarded Control Stack error\\." \ > + "normal_function2 \\(\\) at .*aarch64-gcs.c:$decimal" \ > + "${decimal}\\s+__asm__ volatile\\(\"ret\\\\n\"\\);"] \ > + "continue to SIGSEGV" > + > +gdb_test "print \$_siginfo.si_code" ". = 10" \ > + "test value of si_code when GCS SIGSEGV happens" > +# The GCS grows down, and there are two real frames until main. > +gdb_test "print \$gcspr == \$gcspr_in_main - 16" ". = 1" \ > + "test value of gcspr when GCS SIGSEGV happens" > diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index > ea498c430405..2adfd4b1bcfe 100644 > --- a/gdb/testsuite/lib/gdb.exp > +++ b/gdb/testsuite/lib/gdb.exp > @@ -5094,6 +5094,64 @@ gdb_caching_proc allow_aarch64_mops_tests {} { > return $allow_mops_tests > } > > +# Run a test on the target to see if it supports Aarch64 GCS extensions. > +# Return 0 if so, 1 if it does not. Note this causes a restart of GDB. > + > +gdb_caching_proc allow_aarch64_gcs_tests {} { > + global srcdir subdir gdb_prompt inferior_exited_re > + > + set me "allow_aarch64_gcs_tests" > + > + if { ![is_aarch64_target]} { > + return 0 > + } > + > + # Compile a program that tests the GCS feature. > + set src { > + #include <stdbool.h> > + #include <sys/auxv.h> > + > + /* Feature check for Guarded Control Stack. */ > + #ifndef HWCAP_GCS > + #define HWCAP_GCS (1UL << 32) > + #endif > + > + int main (void) { > + bool gcs_supported = getauxval (AT_HWCAP) & HWCAP_GCS; > + > + /* Return success if GCS is supported. */ > + return !gcs_supported; > + } > + } > + > + if {![gdb_simple_compile $me $src executable]} { > + return 0 > + } > + > + # Compilation succeeded so now run it via gdb. > + clean_restart $obj > + gdb_run_cmd > + gdb_expect { > + -re ".*$inferior_exited_re with code 01.*${gdb_prompt} $" { > + verbose -log "\n$me gcs support not detected" > + set allow_gcs_tests 0 > + } > + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { > + verbose -log "\n$me: gcs support detected" > + set allow_gcs_tests 1 > + } > + default { > + warning "\n$me: default case taken" > + set allow_gcs_tests 0 > + } > + } > + gdb_exit > + remote_file build delete $obj > + > + verbose "$me: returning $allow_gcs_tests" 2 > + return $allow_gcs_tests > +} > + > # A helper that compiles a test case to see if __int128 is supported. > proc gdb_int128_helper {lang} { > return [gdb_can_simple_compile "i128-for-$lang" { 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
On 6/8/25 02:03, Thiago Jung Bauermann wrote: > Also add allow_aarch64_gcs_tests procedure to determine whether Guarded > Control Stack tests should run. > --- > gdb/testsuite/gdb.arch/aarch64-gcs.c | 168 +++++++++++++++++++++++++ > gdb/testsuite/gdb.arch/aarch64-gcs.exp | 78 ++++++++++++ > gdb/testsuite/lib/gdb.exp | 58 +++++++++ > 3 files changed, 304 insertions(+) > create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs.c > create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs.exp > > diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.c b/gdb/testsuite/gdb.arch/aarch64-gcs.c > new file mode 100644 > index 000000000000..8e579de10cdb > --- /dev/null > +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.c > @@ -0,0 +1,168 @@ > +/* This test program is part of GDB, the GNU debugger. > + > + Copyright 2025 Free Software Foundation, Inc. > + > + This program is free software; you can redistribute it and/or modify > + it under the terms of the GNU General Public License as published by > + the Free Software Foundation; either version 3 of the License, or > + (at your option) any later version. > + > + This program is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + GNU General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with this program. If not, see <http://www.gnu.org/licenses/>. */ > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <signal.h> > +#include <sys/auxv.h> > +#include <sys/syscall.h> > +#include <linux/prctl.h> > + > +/* Feature check for Guarded Control Stack. */ > +#ifndef HWCAP_GCS > +#define HWCAP_GCS (1UL << 32) > +#endif > + > +#ifndef PR_GET_SHADOW_STACK_STATUS > +#define PR_GET_SHADOW_STACK_STATUS 74 > +#define PR_SET_SHADOW_STACK_STATUS 75 > +#define PR_SHADOW_STACK_ENABLE (1UL << 0) > +#endif > + > +/* We need to use a macro to call prctl because after GCS is enabled, it's not > + possible to return from the function which enabled it. This is because the > + return address of the calling function isn't on the GCS. */ > +#define my_syscall2(num, arg1, arg2) \ > + ({ \ > + register long _num __asm__("x8") = (num); \ > + register long _arg1 __asm__("x0") = (long)(arg1); \ > + register long _arg2 __asm__("x1") = (long)(arg2); \ > + register long _arg3 __asm__("x2") = 0; \ > + register long _arg4 __asm__("x3") = 0; \ > + register long _arg5 __asm__("x4") = 0; \ > + \ > + __asm__ volatile("svc #0\n" \ > + : "=r"(_arg1) \ > + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ > + "r"(_arg5), "r"(_num) \ > + : "memory", "cc"); \ > + _arg1; \ > + }) > + > +#define get_gcspr(void) \ > + ({ \ > + unsigned long *gcspr; \ > + \ > + /* Get GCSPR_EL0. */ \ > + asm volatile("mrs %0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc"); \ > + \ > + gcspr; \ > + }) > + Is my understanding correct that we're doing all the GCS enablement via the test program as opposed to forcing gdb to enable it at debugging time? > +static unsigned long *handler_gcspr = 0; > + > +static void > +handler (int sig) > +{ > + handler_gcspr = get_gcspr (); > +} > + > +static int __attribute__ ((unused)) > +called_from_gdb (int val) > +{ > + return val + 1; > +} > + > +/* Corrupt the return address to see if GDB will report a SIGSEGV with the expected > + $_siginfo.si_code. */ > +static void __attribute__ ((noinline)) > +normal_function2 (void) > +{ > + /* x30 holds the return address. */ > + register unsigned long x30 __asm__("x30") __attribute__ ((unused)); > + > + /* Cause a GCS exception. */ > + x30 = 0xbadc0ffee; > + __asm__ volatile("ret\n"); > +} > + > +static inline void __attribute__ ((__always_inline__)) > +inline_function2 (void) > +{ > + normal_function2 (); > +} > + > +/* Corrupt the return address to see if GDB will report a GCS error in this > + function's frame . */ > +static void __attribute__ ((noinline)) > +normal_function1 (void) > +{ > + /* x30 holds the return address. */ > + register unsigned long x30 __asm__ ("x30") __attribute__ ((unused)); > + x30 = 0xbadc0ffee; > + inline_function2 (); > +} > + > +static inline void __attribute__ ((__always_inline__)) > +inline_function1 (void) > +{ > + normal_function1 (); > +} > + > +int > +main (void) > +{ > + if (!(getauxval (AT_HWCAP) & HWCAP_GCS)) > + { > + fprintf (stderr, "GCS support not found in AT_HWCAP\n"); > + return EXIT_FAILURE; > + } > + > + /* Force shadow stacks on, our tests *should* be fine with or > + without libc support and with or without this having ended > + up tagged for GCS and enabled by the dynamic linker. We > + can't use the libc prctl() function since we can't return > + from enabling the stack. Also lock GCS if not already > + locked so we can test behaviour when it's locked. */ > + unsigned long gcs_mode; > + int ret = my_syscall2 (__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); > + if (ret) > + { > + fprintf (stderr, "Failed to read GCS state: %d\n", ret); > + return EXIT_FAILURE; > + } > + > + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) > + { > + gcs_mode = PR_SHADOW_STACK_ENABLE; > + ret = my_syscall2 (__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode); > + if (ret) > + { > + fprintf (stderr, "Failed to configure GCS: %d\n", ret); > + return EXIT_FAILURE; > + } > + } > + > + /* This is used by GDB. */ > + __attribute__((unused)) unsigned long *gcspr = get_gcspr (); > + > + struct sigaction act = { 0 }; > + > + act.sa_handler = &handler; /* Break here. */ > + if (sigaction (SIGUSR1, &act, NULL) == -1) > + { > + perror ("sigaction"); > + exit (EXIT_FAILURE); > + } > + > + raise (SIGUSR1); > + > + inline_function1 (); > + > + /* Avoid returning, in case libc doesn't understand GCS. */ > + exit (EXIT_SUCCESS); > +} > diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.exp b/gdb/testsuite/gdb.arch/aarch64-gcs.exp > new file mode 100644 > index 000000000000..211fabf2f86b > --- /dev/null > +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.exp > @@ -0,0 +1,78 @@ > +# Copyright 2025 Free Software Foundation, Inc. > + > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 3 of the License, or > +# (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program. If not, see <http://www.gnu.org/licenses/>. > + > +# Test a binary that uses a Guarded Control Stack. > + > +require allow_aarch64_gcs_tests > + > +standard_testfile > + > +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { > + return > +} > + > +set linespec ${srcfile}:[gdb_get_line_number "Break here"] > + > +if ![runto ${linespec}] { > + return > +} > + > +gdb_test "print \$gcs_features_enabled" \ > + [string_to_regexp { = [ PR_SHADOW_STACK_ENABLE ]}] \ > + "GCS is enabled" > + > +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "GDB knows about gcspr" > +gdb_test "print \$gcspr == gcspr" ". = 1" "GDB has the correct gcspr value" > +gdb_test_no_output "set \$gcspr_in_main = \$gcspr" \ > + "save gcspr value in main for later" > + > +# If the inferior function call fails, we don't want the tests following it > +# to be affected. > +gdb_test_no_output "set unwindonsignal on" > +gdb_test "print called_from_gdb (41)" ". = 42" "call inferior function" > + > +gdb_test "break handler" "Breakpoint \[0-9\]+ .*aarch64-gcs.c, line \[0-9\]+\\." > +gdb_test "handle SIGUSR1 nostop" \ > + ".*\r\nSIGUSR1\\s+No\\s+Yes\\s+Yes\\s+User defined signal 1" \ > + "let the inferior receive SIGUSR1 uninterrupted" > +gdb_test "continue" \ > + ".*\r\nBreakpoint \[0-9\]+, handler \\(sig=10\\) at .*aarch64-gcs.c.*handler_gcspr = get_gcspr \\(\\);" \ > + "continue to signal handler" > + > +gdb_test_no_output "set \$gcspr_in_handler = \$gcspr" \ > + "save gcspr value in handler for later" > +# Select the frame above the <signal handler called> frame, which makes GDB > +# unwind the gcspr from the signal frame GCS context. > +gdb_test "frame 2" "#2 ($hex in )?\\S+ \\(.*\\) (at|from) \\S+.*" \ > + "reached frame 2" > +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "gcspr in frame level 2" > +gdb_test "print \$gcspr == \$gcspr_in_handler + 8" ". = 1" \ > + "gcspr unwound from signal context is correct" > + > +gdb_test "continue" \ > + [multi_line \ > + "Continuing\\." \ > + "" \ > + "Program received signal SIGSEGV, Segmentation fault" \ > + "Guarded Control Stack error\\." \ > + "normal_function2 \\(\\) at .*aarch64-gcs.c:$decimal" \ > + "${decimal}\\s+__asm__ volatile\\(\"ret\\\\n\"\\);"] \ > + "continue to SIGSEGV" > + > +gdb_test "print \$_siginfo.si_code" ". = 10" \ > + "test value of si_code when GCS SIGSEGV happens" > +# The GCS grows down, and there are two real frames until main. > +gdb_test "print \$gcspr == \$gcspr_in_main - 16" ". = 1" \ > + "test value of gcspr when GCS SIGSEGV happens" > diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp > index ea498c430405..2adfd4b1bcfe 100644 > --- a/gdb/testsuite/lib/gdb.exp > +++ b/gdb/testsuite/lib/gdb.exp > @@ -5094,6 +5094,64 @@ gdb_caching_proc allow_aarch64_mops_tests {} { > return $allow_mops_tests > } > > +# Run a test on the target to see if it supports Aarch64 GCS extensions. s/Aarch64/AArch64 > +# Return 0 if so, 1 if it does not. Note this causes a restart of GDB. > + > +gdb_caching_proc allow_aarch64_gcs_tests {} { > + global srcdir subdir gdb_prompt inferior_exited_re > + > + set me "allow_aarch64_gcs_tests" > + > + if { ![is_aarch64_target]} { > + return 0 > + } > + > + # Compile a program that tests the GCS feature. > + set src { > + #include <stdbool.h> > + #include <sys/auxv.h> > + > + /* Feature check for Guarded Control Stack. */ > + #ifndef HWCAP_GCS > + #define HWCAP_GCS (1UL << 32) > + #endif > + > + int main (void) { > + bool gcs_supported = getauxval (AT_HWCAP) & HWCAP_GCS; > + > + /* Return success if GCS is supported. */ > + return !gcs_supported; > + } > + } > + > + if {![gdb_simple_compile $me $src executable]} { > + return 0 > + } > + > + # Compilation succeeded so now run it via gdb. > + clean_restart $obj > + gdb_run_cmd > + gdb_expect { > + -re ".*$inferior_exited_re with code 01.*${gdb_prompt} $" { > + verbose -log "\n$me gcs support not detected" > + set allow_gcs_tests 0 > + } > + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { > + verbose -log "\n$me: gcs support detected" > + set allow_gcs_tests 1 > + } > + default { > + warning "\n$me: default case taken" > + set allow_gcs_tests 0 > + } > + } > + gdb_exit > + remote_file build delete $obj > + > + verbose "$me: returning $allow_gcs_tests" 2 > + return $allow_gcs_tests > +} > + > # A helper that compiles a test case to see if __int128 is supported. > proc gdb_int128_helper {lang} { > return [gdb_can_simple_compile "i128-for-$lang" {
Hello Christina, "Schimpe, Christina" <christina.schimpe@intel.com> writes: > I have one general comment for the 3 patches which add the tests for GCS: > Shouldn't they better be part of the patch which introduces the feature in GDB, > instead of separate commits for testing only? That's a good point and I considered doing that when I was preparing the patch series, but I decided against it because I wanted to make shorter patches to ease review, even if I ended up with more patches. But I do like the logical consistency of each patch already containing the tests for the features it introduces. And it also serves as an illustration of those features for the patch reviewer. I'll change to that approach for v2. Thank you for the suggestion.
Luis Machado <luis.machado@arm.com> writes: > On 6/8/25 02:03, Thiago Jung Bauermann wrote: >> Also add allow_aarch64_gcs_tests procedure to determine whether Guarded >> Control Stack tests should run. >> --- >> gdb/testsuite/gdb.arch/aarch64-gcs.c | 168 +++++++++++++++++++++++++ >> gdb/testsuite/gdb.arch/aarch64-gcs.exp | 78 ++++++++++++ >> gdb/testsuite/lib/gdb.exp | 58 +++++++++ >> 3 files changed, 304 insertions(+) >> create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs.c >> create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs.exp >> >> diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.c b/gdb/testsuite/gdb.arch/aarch64-gcs.c >> new file mode 100644 >> index 000000000000..8e579de10cdb >> --- /dev/null >> +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.c >> @@ -0,0 +1,168 @@ >> +/* This test program is part of GDB, the GNU debugger. >> + >> + Copyright 2025 Free Software Foundation, Inc. >> + >> + This program is free software; you can redistribute it and/or modify >> + it under the terms of the GNU General Public License as published by >> + the Free Software Foundation; either version 3 of the License, or >> + (at your option) any later version. >> + >> + This program is distributed in the hope that it will be useful, >> + but WITHOUT ANY WARRANTY; without even the implied warranty of >> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + GNU General Public License for more details. >> + >> + You should have received a copy of the GNU General Public License >> + along with this program. If not, see <http://www.gnu.org/licenses/>. */ >> + >> +#include <stdio.h> >> +#include <stdlib.h> >> +#include <signal.h> >> +#include <sys/auxv.h> >> +#include <sys/syscall.h> >> +#include <linux/prctl.h> >> + >> +/* Feature check for Guarded Control Stack. */ >> +#ifndef HWCAP_GCS >> +#define HWCAP_GCS (1UL << 32) >> +#endif >> + >> +#ifndef PR_GET_SHADOW_STACK_STATUS >> +#define PR_GET_SHADOW_STACK_STATUS 74 >> +#define PR_SET_SHADOW_STACK_STATUS 75 >> +#define PR_SHADOW_STACK_ENABLE (1UL << 0) >> +#endif >> + >> +/* We need to use a macro to call prctl because after GCS is enabled, it's not >> + possible to return from the function which enabled it. This is because the >> + return address of the calling function isn't on the GCS. */ >> +#define my_syscall2(num, arg1, arg2) \ >> + ({ \ >> + register long _num __asm__("x8") = (num); \ >> + register long _arg1 __asm__("x0") = (long)(arg1); \ >> + register long _arg2 __asm__("x1") = (long)(arg2); \ >> + register long _arg3 __asm__("x2") = 0; \ >> + register long _arg4 __asm__("x3") = 0; \ >> + register long _arg5 __asm__("x4") = 0; \ >> + \ >> + __asm__ volatile("svc #0\n" \ >> + : "=r"(_arg1) \ >> + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ >> + "r"(_arg5), "r"(_num) \ >> + : "memory", "cc"); \ >> + _arg1; \ >> + }) >> + >> +#define get_gcspr(void) \ >> + ({ \ >> + unsigned long *gcspr; \ >> + \ >> + /* Get GCSPR_EL0. */ \ >> + asm volatile("mrs %0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc"); \ >> + \ >> + gcspr; \ >> + }) >> + > > Is my understanding correct that we're doing all the GCS enablement via the test program > as opposed to forcing gdb to enable it at debugging time? Yes, that is correct. Most programs work unchanged with GCS enablement, but a few need to be adapted, Because of that, GCS is being implemented in Linux as an opt-in feature for each program. Following that logic, I made the test program opt-in to GCS as well. >> diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp >> index ea498c430405..2adfd4b1bcfe 100644 >> --- a/gdb/testsuite/lib/gdb.exp >> +++ b/gdb/testsuite/lib/gdb.exp >> @@ -5094,6 +5094,64 @@ gdb_caching_proc allow_aarch64_mops_tests {} { >> return $allow_mops_tests >> } >> >> +# Run a test on the target to see if it supports Aarch64 GCS extensions. > > s/Aarch64/AArch64 Well spotted. Fixed. >> +# Return 0 if so, 1 if it does not. Note this causes a restart of GDB. >> + >> +gdb_caching_proc allow_aarch64_gcs_tests {} { >> + global srcdir subdir gdb_prompt inferior_exited_re >> + >> + set me "allow_aarch64_gcs_tests" >> + >> + if { ![is_aarch64_target]} { >> + return 0 >> + } >> + >> + # Compile a program that tests the GCS feature. >> + set src { >> + #include <stdbool.h> >> + #include <sys/auxv.h> >> + >> + /* Feature check for Guarded Control Stack. */ >> + #ifndef HWCAP_GCS >> + #define HWCAP_GCS (1UL << 32) >> + #endif >> + >> + int main (void) { >> + bool gcs_supported = getauxval (AT_HWCAP) & HWCAP_GCS; >> + >> + /* Return success if GCS is supported. */ >> + return !gcs_supported; >> + } >> + } >> + >> + if {![gdb_simple_compile $me $src executable]} { >> + return 0 >> + } >> + >> + # Compilation succeeded so now run it via gdb. >> + clean_restart $obj >> + gdb_run_cmd >> + gdb_expect { >> + -re ".*$inferior_exited_re with code 01.*${gdb_prompt} $" { >> + verbose -log "\n$me gcs support not detected" >> + set allow_gcs_tests 0 >> + } >> + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { >> + verbose -log "\n$me: gcs support detected" >> + set allow_gcs_tests 1 >> + } >> + default { >> + warning "\n$me: default case taken" >> + set allow_gcs_tests 0 >> + } >> + } >> + gdb_exit >> + remote_file build delete $obj >> + >> + verbose "$me: returning $allow_gcs_tests" 2 >> + return $allow_gcs_tests >> +} >> + >> # A helper that compiles a test case to see if __int128 is supported. >> proc gdb_int128_helper {lang} { >> return [gdb_can_simple_compile "i128-for-$lang" {
diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.c b/gdb/testsuite/gdb.arch/aarch64-gcs.c new file mode 100644 index 000000000000..8e579de10cdb --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.c @@ -0,0 +1,168 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2025 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/auxv.h> +#include <sys/syscall.h> +#include <linux/prctl.h> + +/* Feature check for Guarded Control Stack. */ +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) +#endif + +#ifndef PR_GET_SHADOW_STACK_STATUS +#define PR_GET_SHADOW_STACK_STATUS 74 +#define PR_SET_SHADOW_STACK_STATUS 75 +#define PR_SHADOW_STACK_ENABLE (1UL << 0) +#endif + +/* We need to use a macro to call prctl because after GCS is enabled, it's not + possible to return from the function which enabled it. This is because the + return address of the calling function isn't on the GCS. */ +#define my_syscall2(num, arg1, arg2) \ + ({ \ + register long _num __asm__("x8") = (num); \ + register long _arg1 __asm__("x0") = (long)(arg1); \ + register long _arg2 __asm__("x1") = (long)(arg2); \ + register long _arg3 __asm__("x2") = 0; \ + register long _arg4 __asm__("x3") = 0; \ + register long _arg5 __asm__("x4") = 0; \ + \ + __asm__ volatile("svc #0\n" \ + : "=r"(_arg1) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ + "r"(_arg5), "r"(_num) \ + : "memory", "cc"); \ + _arg1; \ + }) + +#define get_gcspr(void) \ + ({ \ + unsigned long *gcspr; \ + \ + /* Get GCSPR_EL0. */ \ + asm volatile("mrs %0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc"); \ + \ + gcspr; \ + }) + +static unsigned long *handler_gcspr = 0; + +static void +handler (int sig) +{ + handler_gcspr = get_gcspr (); +} + +static int __attribute__ ((unused)) +called_from_gdb (int val) +{ + return val + 1; +} + +/* Corrupt the return address to see if GDB will report a SIGSEGV with the expected + $_siginfo.si_code. */ +static void __attribute__ ((noinline)) +normal_function2 (void) +{ + /* x30 holds the return address. */ + register unsigned long x30 __asm__("x30") __attribute__ ((unused)); + + /* Cause a GCS exception. */ + x30 = 0xbadc0ffee; + __asm__ volatile("ret\n"); +} + +static inline void __attribute__ ((__always_inline__)) +inline_function2 (void) +{ + normal_function2 (); +} + +/* Corrupt the return address to see if GDB will report a GCS error in this + function's frame . */ +static void __attribute__ ((noinline)) +normal_function1 (void) +{ + /* x30 holds the return address. */ + register unsigned long x30 __asm__ ("x30") __attribute__ ((unused)); + x30 = 0xbadc0ffee; + inline_function2 (); +} + +static inline void __attribute__ ((__always_inline__)) +inline_function1 (void) +{ + normal_function1 (); +} + +int +main (void) +{ + if (!(getauxval (AT_HWCAP) & HWCAP_GCS)) + { + fprintf (stderr, "GCS support not found in AT_HWCAP\n"); + return EXIT_FAILURE; + } + + /* Force shadow stacks on, our tests *should* be fine with or + without libc support and with or without this having ended + up tagged for GCS and enabled by the dynamic linker. We + can't use the libc prctl() function since we can't return + from enabling the stack. Also lock GCS if not already + locked so we can test behaviour when it's locked. */ + unsigned long gcs_mode; + int ret = my_syscall2 (__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to read GCS state: %d\n", ret); + return EXIT_FAILURE; + } + + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) + { + gcs_mode = PR_SHADOW_STACK_ENABLE; + ret = my_syscall2 (__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to configure GCS: %d\n", ret); + return EXIT_FAILURE; + } + } + + /* This is used by GDB. */ + __attribute__((unused)) unsigned long *gcspr = get_gcspr (); + + struct sigaction act = { 0 }; + + act.sa_handler = &handler; /* Break here. */ + if (sigaction (SIGUSR1, &act, NULL) == -1) + { + perror ("sigaction"); + exit (EXIT_FAILURE); + } + + raise (SIGUSR1); + + inline_function1 (); + + /* Avoid returning, in case libc doesn't understand GCS. */ + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.exp b/gdb/testsuite/gdb.arch/aarch64-gcs.exp new file mode 100644 index 000000000000..211fabf2f86b --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.exp @@ -0,0 +1,78 @@ +# Copyright 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Test a binary that uses a Guarded Control Stack. + +require allow_aarch64_gcs_tests + +standard_testfile + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { + return +} + +set linespec ${srcfile}:[gdb_get_line_number "Break here"] + +if ![runto ${linespec}] { + return +} + +gdb_test "print \$gcs_features_enabled" \ + [string_to_regexp { = [ PR_SHADOW_STACK_ENABLE ]}] \ + "GCS is enabled" + +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "GDB knows about gcspr" +gdb_test "print \$gcspr == gcspr" ". = 1" "GDB has the correct gcspr value" +gdb_test_no_output "set \$gcspr_in_main = \$gcspr" \ + "save gcspr value in main for later" + +# If the inferior function call fails, we don't want the tests following it +# to be affected. +gdb_test_no_output "set unwindonsignal on" +gdb_test "print called_from_gdb (41)" ". = 42" "call inferior function" + +gdb_test "break handler" "Breakpoint \[0-9\]+ .*aarch64-gcs.c, line \[0-9\]+\\." +gdb_test "handle SIGUSR1 nostop" \ + ".*\r\nSIGUSR1\\s+No\\s+Yes\\s+Yes\\s+User defined signal 1" \ + "let the inferior receive SIGUSR1 uninterrupted" +gdb_test "continue" \ + ".*\r\nBreakpoint \[0-9\]+, handler \\(sig=10\\) at .*aarch64-gcs.c.*handler_gcspr = get_gcspr \\(\\);" \ + "continue to signal handler" + +gdb_test_no_output "set \$gcspr_in_handler = \$gcspr" \ + "save gcspr value in handler for later" +# Select the frame above the <signal handler called> frame, which makes GDB +# unwind the gcspr from the signal frame GCS context. +gdb_test "frame 2" "#2 ($hex in )?\\S+ \\(.*\\) (at|from) \\S+.*" \ + "reached frame 2" +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "gcspr in frame level 2" +gdb_test "print \$gcspr == \$gcspr_in_handler + 8" ". = 1" \ + "gcspr unwound from signal context is correct" + +gdb_test "continue" \ + [multi_line \ + "Continuing\\." \ + "" \ + "Program received signal SIGSEGV, Segmentation fault" \ + "Guarded Control Stack error\\." \ + "normal_function2 \\(\\) at .*aarch64-gcs.c:$decimal" \ + "${decimal}\\s+__asm__ volatile\\(\"ret\\\\n\"\\);"] \ + "continue to SIGSEGV" + +gdb_test "print \$_siginfo.si_code" ". = 10" \ + "test value of si_code when GCS SIGSEGV happens" +# The GCS grows down, and there are two real frames until main. +gdb_test "print \$gcspr == \$gcspr_in_main - 16" ". = 1" \ + "test value of gcspr when GCS SIGSEGV happens" diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index ea498c430405..2adfd4b1bcfe 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -5094,6 +5094,64 @@ gdb_caching_proc allow_aarch64_mops_tests {} { return $allow_mops_tests } +# Run a test on the target to see if it supports Aarch64 GCS extensions. +# Return 0 if so, 1 if it does not. Note this causes a restart of GDB. + +gdb_caching_proc allow_aarch64_gcs_tests {} { + global srcdir subdir gdb_prompt inferior_exited_re + + set me "allow_aarch64_gcs_tests" + + if { ![is_aarch64_target]} { + return 0 + } + + # Compile a program that tests the GCS feature. + set src { + #include <stdbool.h> + #include <sys/auxv.h> + + /* Feature check for Guarded Control Stack. */ + #ifndef HWCAP_GCS + #define HWCAP_GCS (1UL << 32) + #endif + + int main (void) { + bool gcs_supported = getauxval (AT_HWCAP) & HWCAP_GCS; + + /* Return success if GCS is supported. */ + return !gcs_supported; + } + } + + if {![gdb_simple_compile $me $src executable]} { + return 0 + } + + # Compilation succeeded so now run it via gdb. + clean_restart $obj + gdb_run_cmd + gdb_expect { + -re ".*$inferior_exited_re with code 01.*${gdb_prompt} $" { + verbose -log "\n$me gcs support not detected" + set allow_gcs_tests 0 + } + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { + verbose -log "\n$me: gcs support detected" + set allow_gcs_tests 1 + } + default { + warning "\n$me: default case taken" + set allow_gcs_tests 0 + } + } + gdb_exit + remote_file build delete $obj + + verbose "$me: returning $allow_gcs_tests" 2 + return $allow_gcs_tests +} + # A helper that compiles a test case to see if __int128 is supported. proc gdb_int128_helper {lang} { return [gdb_can_simple_compile "i128-for-$lang" {