diff mbox series

[8/8] GDB: testsuite: Add gdb.arch/aarch64-gcs-disp-step.exp testcase

Message ID 20250608010338.2234530-9-thiago.bauermann@linaro.org
State New
Headers show
Series AArch64 Guarded Control Stack support | expand

Commit Message

Thiago Jung Bauermann June 8, 2025, 1:03 a.m. UTC
It exercises GCS with displaced stepping by putting the breakpoint on
the bl instruction to force GDB to copy it to the displaced stepping
buffer.

In this case GDB needs to manually manage the Guarded Control Stack.
---
 .../gdb.arch/aarch64-gcs-disp-step.c          | 140 ++++++++++++++++++
 .../gdb.arch/aarch64-gcs-disp-step.exp        |  90 +++++++++++
 2 files changed, 230 insertions(+)
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.c
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.exp
diff mbox series

Patch

diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.c b/gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.c
new file mode 100644
index 000000000000..3d895350ae42
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.c
@@ -0,0 +1,140 @@ 
+/* 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 <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 int __attribute__ ((noinline))
+function2 (void)
+{
+  return EXIT_SUCCESS;
+}
+
+/* Put branch and link instructions being tested into their own functions so
+   that the program returns one level up in the stack after the displaced
+   stepped instruction.  This tests that GDB doesn't leave the GCS out of sync
+   with the regular stack.  */
+
+static int __attribute__ ((noinline))
+function_bl (void)
+{
+  register int x0 __asm__("x0");
+
+  __asm__ ("bl function2\n"
+	   : "=r"(x0)
+	   :
+	   : "x30");
+
+  return x0;
+}
+
+static int __attribute__ ((noinline))
+function_blr (void)
+{
+  register int x0 __asm__("x0");
+
+  __asm__ ("blr %1\n"
+	   : "=r"(x0)
+	   : "r"(&function2)
+	   : "x30");
+
+  return x0;
+}
+
+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.  */
+  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;
+	}
+    }
+
+  int ret1 = function_bl ();
+  int ret2 = function_blr ();
+
+  /* Avoid returning, in case libc doesn't understand GCS.  */
+  exit (ret1 + ret2);
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.exp b/gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.exp
new file mode 100644
index 000000000000..10b09a4d980e
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.exp
@@ -0,0 +1,90 @@ 
+# 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 displaced stepping in a program that uses a Guarded Control Stack.
+
+require allow_aarch64_gcs_tests
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return
+}
+
+if ![runto_main] {
+    return
+}
+
+gdb_test_no_output "set breakpoint auto-hw off"
+gdb_test_no_output "set displaced-stepping on"
+
+# Get address of the branch and link instructions of interest.
+set addr_bl 0
+set test "get address of bl instruction"
+gdb_test_multiple "disassemble function_bl" $test -lbl {
+    -re "\r\n\\s+($hex) <\\+${decimal}>:\\s+bl\\s+${hex} <function2>(?=\r\n)" {
+	set addr_bl $expect_out(1,string)
+	exp_continue
+    }
+    -re "$::gdb_prompt \$" {
+	gdb_assert { $addr_bl != 0 } $test
+    }
+}
+
+set addr_blr 0
+set test "get address of blr instruction"
+gdb_test_multiple "disassemble function_blr" $test -lbl {
+    -re "\r\n\\s+($hex) <\\+${decimal}>:\\s+blr\\s+x${decimal}(?=\r\n)" {
+	set addr_blr $expect_out(1,string)
+	exp_continue
+    }
+    -re "$::gdb_prompt \$" {
+	gdb_assert { $addr_blr != 0 } $test
+    }
+}
+
+if { $addr_bl == 0 || $addr_blr == 0 } {
+    return
+}
+
+gdb_test "break *$addr_bl" \
+    "Breakpoint $decimal at $hex: file .*aarch64-gcs-disp-step.c, line ${decimal}." \
+    "set breakpoint at bl instruction"
+
+gdb_test "break *$addr_blr" \
+    "Breakpoint $decimal at $hex: file .*aarch64-gcs-disp-step.c, line ${decimal}." \
+    "set breakpoint at blr instruction"
+
+gdb_test "continue" \
+    [multi_line \
+	 {Continuing\.} \
+	 "" \
+	 "Breakpoint $decimal, function_bl \\(\\) at .*aarch64-gcs-disp-step.c:${decimal}(?: \\\[GCS error\\\])?" \
+	 {[^\r\n]+"bl function2\\n"}] \
+    "continue to breakpoint at bl"
+
+gdb_test "continue" \
+    [multi_line \
+	 {Continuing\.} \
+	 "" \
+	 "Breakpoint $decimal, $hex in function_blr \\(\\) at .*aarch64-gcs-disp-step.c:${decimal}(?: \\\[GCS error\\\])?" \
+	 {[^\r\n]+"blr %1\\n"}] \
+    "continue to breakpoint at blr"
+
+gdb_test "continue" \
+    [multi_line \
+	 "Continuing\\." \
+	 "\\\[Inferior 1 \\(process $decimal\\) exited normally\\\]"] \
+    "continue until inferior exits"