@@ -32,6 +32,7 @@
#include "symtab.h"
#include "tramp-frame.h"
#include "trad-frame.h"
+#include "dwarf2/frame.h"
#include "target.h"
#include "target/target.h"
#include "expop.h"
@@ -2561,6 +2562,54 @@ aarch64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
return gcspr;
}
+/* Implement Guarded Control Stack Pointer Register unwinding. For each
+ previous GCS pointer check if its address is still in the GCS memory
+ range. If it's outside the range set the returned value to unavailable,
+ otherwise return a value containing the new GCS pointer. */
+
+static value *
+aarch64_linux_dwarf2_prev_gcspr (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 gcspr = extract_unsigned_integer (v->contents_all ().data (),
+ size, byte_order);
+
+ /* Starting with v6.13, the Linux kernel supports Guarded Control
+ Stack. Using /proc/PID/smaps we can only check if the current
+ GCSPR points to GCS memory. Only if this is the case a valid
+ previous GCS pointer can be calculated. */
+ std::pair<CORE_ADDR, CORE_ADDR> range;
+ if (linux_address_in_shadow_stack_mem_range (gcspr, &range))
+ {
+ /* The GCS grows downwards. To compute the previous GCS pointer,
+ we need to increment the GCSPR. */
+ CORE_ADDR new_gcspr = gcspr + 8;
+
+ /* If NEW_GCSPR points to the end of or before (<=) the current
+ GCS memory range we consider NEW_GCSPR as valid. */
+ if (new_gcspr <= range.second)
+ return frame_unwind_got_address (this_frame, regnum, new_gcspr);
+ }
+ }
+
+ /* Return a value which is marked as unavailable in case we could not
+ calculate a valid previous GCS 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;
+}
+
/* AArch64 Linux implementation of the report_signal_info gdbarch
hook. Displays information about possible memory tag violations. */
@@ -3134,8 +3183,11 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
aarch64_use_target_description_from_corefile_notes);
if (tdep->has_gcs_linux ())
- set_gdbarch_get_shadow_stack_pointer (gdbarch,
+ {
+ set_gdbarch_get_shadow_stack_pointer (gdbarch,
aarch64_linux_get_shadow_stack_pointer);
+ tdep->fn_prev_gcspr = aarch64_linux_dwarf2_prev_gcspr;
+ }
}
#if GDB_SELF_TEST
@@ -1408,6 +1408,12 @@ aarch64_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum,
return;
}
}
+ if (tdep->has_gcs () && tdep->fn_prev_gcspr != nullptr
+ && regnum == tdep->gcs_reg_base)
+ {
+ reg->how = DWARF2_FRAME_REG_FN;
+ reg->loc.fn = tdep->fn_prev_gcspr;
+ }
}
/* Implement the execute_dwarf_cfa_vendor_op method. */
@@ -23,6 +23,7 @@
#define GDB_AARCH64_TDEP_H
#include "arch/aarch64.h"
+#include "dwarf2/frame.h"
#include "displaced-stepping.h"
#include "infrun.h"
#include "gdbarch.h"
@@ -190,6 +191,9 @@ struct aarch64_gdbarch_tdep : gdbarch_tdep_base
available. */
int gcs_linux_reg_base = -1;
+ /* Function to unwind the GCSPR from the given frame. */
+ fn_prev_register fn_prev_gcspr = nullptr;
+
/* Returns true if the target supports GCS. */
bool
has_gcs () const
new file mode 100644
@@ -0,0 +1,105 @@
+/* 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; \
+ })
+
+static int __attribute__ ((noinline))
+call2 ()
+{
+ return 42; /* Break call2. */
+}
+
+static int __attribute__ ((noinline))
+call1 ()
+{
+ return call2 (); /* Break call1. */
+}
+
+int
+main ()
+{
+ 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;
+ }
+ }
+
+ call1 (); /* Break main. */
+
+ /* Avoid returning, in case libc doesn't understand GCS. */
+ exit (EXIT_SUCCESS);
+}
new file mode 100644
@@ -0,0 +1,132 @@
+# 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 the GDB return command in a program that uses a Guarded Control Stack.
+# Based on the return tests in gdb.arch/amd64-shadow-stack-cmds.exp.
+
+require allow_aarch64_gcs_tests
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+ return
+}
+
+set main_line [gdb_get_line_number "Break main"]
+set call1_line [gdb_get_line_number "Break call1"]
+set call2_line [gdb_get_line_number "Break call2"]
+
+if { ![runto ${main_line}] } {
+ return
+}
+
+proc restart_and_run_infcall_call2 {} {
+ global binfile call2_line
+ clean_restart ${binfile}
+ if { ![runto_main] } {
+ return
+ }
+ set inside_infcall_str "The program being debugged stopped while in a function called from GDB"
+ gdb_breakpoint ${call2_line}
+ gdb_continue_to_breakpoint "Break call2" ".*Break call2.*"
+ gdb_test "call (int) call2()" \
+ "Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*"
+}
+
+with_test_prefix "test inferior call and continue" {
+ gdb_breakpoint ${call1_line}
+ 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 GCS 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 GCS return 'above' an inferior call" \
+ "Make.*return now\\? \\(y or n\\) " "y"
+
+ gdb_continue_to_end
+}
+
+clean_restart ${binfile}
+if { ![runto ${main_line}] } {
+ return
+}
+
+# Extract GCS pointer inside main, call1 and call2 function.
+gdb_breakpoint ${call1_line}
+gdb_breakpoint ${call2_line}
+set gcspr_main [get_valueof /x "\$gcspr" 0 "get value of gcspr in main"]
+gdb_continue_to_breakpoint "Break call1" ".*Break call1.*"
+set gcspr_call1 [get_valueof /x "\$gcspr" 0 "get value of gcspr in call1"]
+gdb_continue_to_breakpoint "Break call2" ".*Break call2.*"
+set gcspr_call2 [get_valueof /x "\$gcspr" 0 "get value of gcspr in call2"]
+
+with_test_prefix "test frame level update" {
+ gdb_test "up" "call1.*" "move to frame 1"
+ gdb_test "print /x \$gcspr" "= $gcspr_call1" "check gcspr of frame 1"
+ gdb_test "up" "main.*" "move to frame 2"
+ gdb_test "print /x \$gcspr" "= $gcspr_main" "check gcspr of frame 2"
+ gdb_test "frame 0" "call2.*" "move to frame 0"
+ gdb_test "print /x \$gcspr" "= $gcspr_call2" "check gcspr of frame 0"
+}
+
+with_test_prefix "test return from current frame" {
+ gdb_test "return (int) 1" "#0.*call1.*" \
+ "Test GCS return from current frame" \
+ "Make.*return now\\? \\(y or n\\) " "y"
+
+ # Potential GCS 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
+}
+
+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 GCS return from past frame" \
+ "Make.*return now\\? \\(y or n\\) " "y"
+
+ # Potential GCS 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
+}