Message ID | 20250608010338.2234530-8-thiago.bauermann@linaro.org |
---|---|
State | New |
Headers | show |
Series | AArch64 Guarded Control Stack support | expand |
On 6/8/25 02:03, Thiago Jung Bauermann wrote: > It tests both gcore and OS-generated core files. > --- > gdb/testsuite/gdb.arch/aarch64-gcs-core.c | 124 ++++++++++++++++++++ > gdb/testsuite/gdb.arch/aarch64-gcs-core.exp | 105 +++++++++++++++++ > gdb/testsuite/lib/gdb.exp | 4 +- > 3 files changed, 231 insertions(+), 2 deletions(-) > create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-core.c > create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-core.exp > > diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.c b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c > new file mode 100644 > index 000000000000..d04bd76e0799 > --- /dev/null > +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c > @@ -0,0 +1,124 @@ > +/* 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 <sys/auxv.h> > +#include <linux/prctl.h> > +#include <sys/syscall.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; \ > + }) > + > +/* Corrupt the return address to see if GDB will report a SIGSEGV with the > + expected > + $_siginfo.si_code. */ > +static void __attribute__ ((noinline)) > +function (unsigned long *gcspr) > +{ > + /* x30 holds the return address. */ > + register long x30 __asm__("x30") __attribute__ ((unused)); > + > + /* Print GCSPR to stdout so that the testcase can capture it. */ > + printf ("%p\n", get_gcspr ()); > + fflush (stdout); > + > + /* Cause a GCS exception. */ > + x30 = 0xbadc0ffee; > + __asm__ volatile("ret\n"); > +} > + > +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; > + } > + } > + > + unsigned long *gcspr = get_gcspr (); > + > + /* Pass gscpr to function just so it's used for something. */ > + function (gcspr); /* Break here. */ > + > + /* Avoid returning, in case libc doesn't understand GCS. */ > + exit (EXIT_SUCCESS); > +} > diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp > new file mode 100644 > index 000000000000..17bbb5e4ded9 > --- /dev/null > +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp > @@ -0,0 +1,105 @@ > +# 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 reading and writing the core dump of 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 > +} > + > +# Continue until a crash. The line with the hex number is optional because > +# it's printed by the test program, and doesn't appear in the Expect buffer > +# when testing a remote target. > +gdb_test "continue" \ > + [multi_line \ > + "Continuing\\." \ > + "($hex\r\n)?" \ > + "Program received signal SIGSEGV, Segmentation fault" \ > + "Guarded Control Stack error\\." \ > + "function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \ > + {.*__asm__ volatile\("ret\\n"\);}] \ > + "continue to SIGSEGV" > + > +set gcspr_in_gcore [get_valueof "/x" "\$gcspr" "*unknown*"] > + > +# Generate the gcore core file. > +set gcore_filename [standard_output_file "${testfile}.gcore"] > +set gcore_generated [gdb_gcore_cmd "$gcore_filename" "generate gcore file"] > + > +# Obtain an OS-generated core file. > +set core_filename [core_find $binfile {} {} "${binfile}.out"] > +set core_generated [expr {$core_filename != ""}] > +set os_core_name "${binfile}.core" > +remote_exec build "mv $core_filename $os_core_name" > +set core_filename $os_core_name > + > +# At this point we have a couple of core files, the gcore one generated by > +# GDB and the one generated by the operating system. Make sure GDB can > +# read both correctly. > + > +proc check_core_file {core_filename saved_gcspr} { > + global decimal hex > + > + # Load the core file. > + if [gdb_test "core $core_filename" \ > + [multi_line \ > + "Core was generated by .*\\." \ > + "Program terminated with signal SIGSEGV, Segmentation fault" \ > + "Guarded Control Stack error\\." \ > + "#0 function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \ > + "$decimal.*__asm__ volatile\\(\"ret\\\\n\"\\);"] \ > + "load core file"] { > + return -1 > + } > + > + # Check the value of GCSPR in the core file. > + gdb_test "print/x \$gcspr" "\\$\[0-9\]+ = $saved_gcspr" \ > + "gcspr contents from core file" > +} > + > +if {$gcore_generated} { > + clean_restart $binfile > + > + with_test_prefix "gcore corefile" { > + check_core_file $gcore_filename $gcspr_in_gcore > + } > +} else { > + fail "gcore corefile not generated" > +} > + > +if {$core_generated} { > + clean_restart $binfile > + > + with_test_prefix "OS corefile" { > + set out_id [open ${binfile}.out "r"] > + set gcspr_in_core [gets $out_id] > + > + close $out_id > + check_core_file $core_filename $gcspr_in_core > + } > +} else { > + untested "OS corefile not generated" > +} > diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp > index 2adfd4b1bcfe..cf0b7cb9562d 100644 > --- a/gdb/testsuite/lib/gdb.exp > +++ b/gdb/testsuite/lib/gdb.exp > @@ -9408,7 +9408,7 @@ proc remove_core {pid {test ""}} { > } > } > > -proc core_find {binfile {deletefiles {}} {arg ""}} { > +proc core_find {binfile {deletefiles {}} {arg ""} {output_file "/dev/null"}} { > global objdir subdir > > set destcore "$binfile.core" > @@ -9430,7 +9430,7 @@ proc core_find {binfile {deletefiles {}} {arg ""}} { > set found 0 > set coredir [standard_output_file coredir.[getpid]] > file mkdir $coredir > - catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >/dev/null 2>&1\"" > + catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >${output_file} 2>&1\"" The above change seems like an improvement on the core testing helpers. What does it help with? > # remote_exec host "${binfile}" > foreach i "${coredir}/core ${coredir}/core.coremaker.c ${binfile}.core" { > if [remote_file build exists $i] {
Luis Machado <luis.machado@arm.com> writes: > On 6/8/25 02:03, Thiago Jung Bauermann wrote: >> diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp >> index 2adfd4b1bcfe..cf0b7cb9562d 100644 >> --- a/gdb/testsuite/lib/gdb.exp >> +++ b/gdb/testsuite/lib/gdb.exp >> @@ -9408,7 +9408,7 @@ proc remove_core {pid {test ""}} { >> } >> } >> >> -proc core_find {binfile {deletefiles {}} {arg ""}} { >> +proc core_find {binfile {deletefiles {}} {arg ""} {output_file "/dev/null"}} { >> global objdir subdir >> >> set destcore "$binfile.core" >> @@ -9430,7 +9430,7 @@ proc core_find {binfile {deletefiles {}} {arg ""}} { >> set found 0 >> set coredir [standard_output_file coredir.[getpid]] >> file mkdir $coredir >> - catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >/dev/null 2>&1\"" >> + catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >${output_file} 2>&1\"" > > The above change seems like an improvement on the core testing helpers. What does it help > with? Ah, sorry about not explaining it. The test program prints to stdout the value of the GCSPR right before crashing. This change allows the testcase to recover the program output and read the GCSPR value so that it can check whether GDB got it right. I'll add a note to the commit message with this explanation. >> # remote_exec host "${binfile}" >> foreach i "${coredir}/core ${coredir}/core.coremaker.c ${binfile}.core" { >> if [remote_file build exists $i] {
diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.c b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c new file mode 100644 index 000000000000..d04bd76e0799 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c @@ -0,0 +1,124 @@ +/* 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 <sys/auxv.h> +#include <linux/prctl.h> +#include <sys/syscall.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; \ + }) + +/* Corrupt the return address to see if GDB will report a SIGSEGV with the + expected + $_siginfo.si_code. */ +static void __attribute__ ((noinline)) +function (unsigned long *gcspr) +{ + /* x30 holds the return address. */ + register long x30 __asm__("x30") __attribute__ ((unused)); + + /* Print GCSPR to stdout so that the testcase can capture it. */ + printf ("%p\n", get_gcspr ()); + fflush (stdout); + + /* Cause a GCS exception. */ + x30 = 0xbadc0ffee; + __asm__ volatile("ret\n"); +} + +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; + } + } + + unsigned long *gcspr = get_gcspr (); + + /* Pass gscpr to function just so it's used for something. */ + function (gcspr); /* Break here. */ + + /* Avoid returning, in case libc doesn't understand GCS. */ + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp new file mode 100644 index 000000000000..17bbb5e4ded9 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp @@ -0,0 +1,105 @@ +# 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 reading and writing the core dump of 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 +} + +# Continue until a crash. The line with the hex number is optional because +# it's printed by the test program, and doesn't appear in the Expect buffer +# when testing a remote target. +gdb_test "continue" \ + [multi_line \ + "Continuing\\." \ + "($hex\r\n)?" \ + "Program received signal SIGSEGV, Segmentation fault" \ + "Guarded Control Stack error\\." \ + "function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \ + {.*__asm__ volatile\("ret\\n"\);}] \ + "continue to SIGSEGV" + +set gcspr_in_gcore [get_valueof "/x" "\$gcspr" "*unknown*"] + +# Generate the gcore core file. +set gcore_filename [standard_output_file "${testfile}.gcore"] +set gcore_generated [gdb_gcore_cmd "$gcore_filename" "generate gcore file"] + +# Obtain an OS-generated core file. +set core_filename [core_find $binfile {} {} "${binfile}.out"] +set core_generated [expr {$core_filename != ""}] +set os_core_name "${binfile}.core" +remote_exec build "mv $core_filename $os_core_name" +set core_filename $os_core_name + +# At this point we have a couple of core files, the gcore one generated by +# GDB and the one generated by the operating system. Make sure GDB can +# read both correctly. + +proc check_core_file {core_filename saved_gcspr} { + global decimal hex + + # Load the core file. + if [gdb_test "core $core_filename" \ + [multi_line \ + "Core was generated by .*\\." \ + "Program terminated with signal SIGSEGV, Segmentation fault" \ + "Guarded Control Stack error\\." \ + "#0 function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \ + "$decimal.*__asm__ volatile\\(\"ret\\\\n\"\\);"] \ + "load core file"] { + return -1 + } + + # Check the value of GCSPR in the core file. + gdb_test "print/x \$gcspr" "\\$\[0-9\]+ = $saved_gcspr" \ + "gcspr contents from core file" +} + +if {$gcore_generated} { + clean_restart $binfile + + with_test_prefix "gcore corefile" { + check_core_file $gcore_filename $gcspr_in_gcore + } +} else { + fail "gcore corefile not generated" +} + +if {$core_generated} { + clean_restart $binfile + + with_test_prefix "OS corefile" { + set out_id [open ${binfile}.out "r"] + set gcspr_in_core [gets $out_id] + + close $out_id + check_core_file $core_filename $gcspr_in_core + } +} else { + untested "OS corefile not generated" +} diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index 2adfd4b1bcfe..cf0b7cb9562d 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -9408,7 +9408,7 @@ proc remove_core {pid {test ""}} { } } -proc core_find {binfile {deletefiles {}} {arg ""}} { +proc core_find {binfile {deletefiles {}} {arg ""} {output_file "/dev/null"}} { global objdir subdir set destcore "$binfile.core" @@ -9430,7 +9430,7 @@ proc core_find {binfile {deletefiles {}} {arg ""}} { set found 0 set coredir [standard_output_file coredir.[getpid]] file mkdir $coredir - catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >/dev/null 2>&1\"" + catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >${output_file} 2>&1\"" # remote_exec host "${binfile}" foreach i "${coredir}/core ${coredir}/core.coremaker.c ${binfile}.core" { if [remote_file build exists $i] {