diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c index 88d4ad9f52a..bb46cb2a20b 100644 --- a/gdb/aarch64-linux-tdep.c +++ b/gdb/aarch64-linux-tdep.c @@ -2535,6 +2535,32 @@ aarch64_linux_tagged_address_p (struct gdbarch *gdbarch, CORE_ADDR address) return true; } +/* Implement the "get_shadow_stack_pointer" gdbarch method. */ + +static std::optional +aarch64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache, + bool &shadow_stack_enabled) +{ + aarch64_gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + shadow_stack_enabled = false; + + if (!tdep->has_gcs_linux ()) + return {}; + + uint64_t features_enabled; + register_status status = regcache->cooked_read (tdep->gcs_linux_reg_base, + &features_enabled); + if (status != REG_VALID) + error (_("Can't read $gcs_features_enabled.")); + + CORE_ADDR gcspr; + status = regcache->cooked_read (tdep->gcs_reg_base, &gcspr); + if (status != REG_VALID) + error (_("Can't read $gcspr.")); + + shadow_stack_enabled = features_enabled & PR_SHADOW_STACK_ENABLE; + return gcspr; +} /* AArch64 Linux implementation of the report_signal_info gdbarch hook. Displays information about possible memory tag violations. */ @@ -3109,6 +3135,10 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) sections. */ set_gdbarch_use_target_description_from_corefile_notes (gdbarch, aarch64_use_target_description_from_corefile_notes); + + if (tdep->has_gcs_linux ()) + set_gdbarch_get_shadow_stack_pointer (gdbarch, + aarch64_linux_get_shadow_stack_pointer); } #if GDB_SELF_TEST diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index 04f3946b5ea..86b083e36cc 100644 --- a/gdb/aarch64-tdep.c +++ b/gdb/aarch64-tdep.c @@ -1910,6 +1910,23 @@ aarch64_push_gcs_entry (regcache *regs, CORE_ADDR lr_value) regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr); } +/* Remove the newest entry from the Guarded Control Stack. */ + +static void +aarch64_pop_gcs_entry (regcache *regs) +{ + gdbarch *arch = regs->arch (); + aarch64_gdbarch_tdep *tdep = gdbarch_tdep (arch); + CORE_ADDR gcs_addr; + + register_status status = regs->cooked_read (tdep->gcs_reg_base, &gcs_addr); + if (status != REG_VALID) + error (_("Can't read $gcspr.")); + + /* Update GCSPR. */ + regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr + 8); +} + /* Implement the "shadow_stack_push" gdbarch method. */ static void @@ -3601,6 +3618,9 @@ struct aarch64_displaced_step_copy_insn_closure /* PC adjustment offset after displaced stepping. If 0, then we don't write the PC back, assuming the PC is already the right address. */ int32_t pc_adjust = 0; + + /* True if it's a branch instruction that saves the link register. */ + bool linked_branch = false; }; /* Data when visiting instructions for displaced stepping. */ @@ -3652,6 +3672,12 @@ aarch64_displaced_step_b (const int is_bl, const int32_t offset, /* Update LR. */ regcache_cooked_write_unsigned (dsd->regs, AARCH64_LR_REGNUM, data->insn_addr + 4); + dsd->dsc->linked_branch = true; + bool gcs_is_enabled; + gdbarch_get_shadow_stack_pointer (dsd->regs->arch (), dsd->regs, + gcs_is_enabled); + if (gcs_is_enabled) + aarch64_push_gcs_entry (dsd->regs, data->insn_addr + 4); } } @@ -3810,6 +3836,12 @@ aarch64_displaced_step_others (const uint32_t insn, aarch64_emit_insn (dsd->insn_buf, insn & 0xffdfffff); regcache_cooked_write_unsigned (dsd->regs, AARCH64_LR_REGNUM, data->insn_addr + 4); + dsd->dsc->linked_branch = true; + bool gcs_is_enabled; + gdbarch_get_shadow_stack_pointer (dsd->regs->arch (), dsd->regs, + gcs_is_enabled); + if (gcs_is_enabled) + aarch64_push_gcs_entry (dsd->regs, data->insn_addr + 4); } else aarch64_emit_insn (dsd->insn_buf, insn); @@ -3906,20 +3938,24 @@ aarch64_displaced_step_fixup (struct gdbarch *gdbarch, CORE_ADDR from, CORE_ADDR to, struct regcache *regs, bool completed_p) { + aarch64_displaced_step_copy_insn_closure *dsc + = (aarch64_displaced_step_copy_insn_closure *) dsc_; CORE_ADDR pc = regcache_read_pc (regs); - /* If the displaced instruction didn't complete successfully then all we - need to do is restore the program counter. */ + /* If the displaced instruction didn't complete successfully then we need + to restore the program counter, and perhaps the Guarded Control Stack. */ if (!completed_p) { + bool gcs_is_enabled; + gdbarch_get_shadow_stack_pointer (gdbarch, regs, gcs_is_enabled); + if (dsc->linked_branch && gcs_is_enabled) + aarch64_pop_gcs_entry (regs); + pc = from + (pc - to); regcache_write_pc (regs, pc); return; } - aarch64_displaced_step_copy_insn_closure *dsc - = (aarch64_displaced_step_copy_insn_closure *) dsc_; - displaced_debug_printf ("PC after stepping: %s (was %s).", paddress (gdbarch, pc), paddress (gdbarch, to)); diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h index a3b331f4e1b..a4f0f537482 100644 --- a/gdb/linux-tdep.h +++ b/gdb/linux-tdep.h @@ -31,6 +31,13 @@ struct regcache; #define SEGV_CPERR 10 /* Control protection error. */ #endif +/* Flag which enables shadow stack in PR_SET_SHADOW_STACK_STATUS prctl. */ +#ifndef PR_SHADOW_STACK_ENABLE +#define PR_SHADOW_STACK_ENABLE (1UL << 0) +#define PR_SHADOW_STACK_WRITE (1UL << 1) +#define PR_SHADOW_STACK_PUSH (1UL << 2) +#endif + /* Enum used to define the extra fields of the siginfo type used by an architecture. */ enum linux_siginfo_extra_field_values 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 00000000000..3d895350ae4 --- /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 . */ + +#include +#include +#include +#include +#include + +/* 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 00000000000..2359d96aeeb --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.exp @@ -0,0 +1,86 @@ +# 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 . + +# 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} (?=\r\n)" { + set addr_bl $expect_out(1,string) + exp_continue + } + -re -wrap "" { + 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 -wrap "" { + 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_continue_to_end