Message ID | 20250618055445.709416-8-thiago.bauermann@linaro.org |
---|---|
State | New |
Headers | show |
Series | AArch64 Guarded Control Stack support | expand |
On 6/18/25 06:54, Thiago Jung Bauermann wrote: > When doing displaced step on a branch and link instruction with the Guarded > Control Stack enabled, it's necessary to manually push and pop the GCS > entry for the function call since GDB writes a simple branch instruction > rather than a branch and link instruction in the displaced step buffer. > > The testcase 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 situation GDB needs to manually > manage the Guarded Control Stack. > --- > gdb/aarch64-linux-tdep.c | 30 ++++ > gdb/aarch64-tdep.c | 47 +++++- > gdb/linux-tdep.h | 7 + > .../gdb.arch/aarch64-gcs-disp-step.c | 140 ++++++++++++++++++ > .../gdb.arch/aarch64-gcs-disp-step.exp | 90 +++++++++++ > 5 files changed, 309 insertions(+), 5 deletions(-) > create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.c > create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-disp-step.exp > > Changes since v1: > - Use aarch64_gdbarch_tdep::has_gcs_linux in > aarch64_linux_get_shadow_stack_pointer and aarch64_linux_init_abi instead > of has_gcs. > - Moved aarch64-gcs-disp-step.exp to this patch. > > diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c > index 6bd9dfc47d81..63c6f6b76b92 100644 > --- a/gdb/aarch64-linux-tdep.c > +++ b/gdb/aarch64-linux-tdep.c > @@ -2534,6 +2534,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<CORE_ADDR> > +aarch64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache, > + bool &shadow_stack_enabled) > +{ > + aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch); > + shadow_stack_enabled = false; > + > + if (!tdep->has_gcs_linux ()) > + return {}; > + > + uint64_t features_enabled; > + enum 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. */ > @@ -3106,6 +3132,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 0e9e6644dd0a..e21c4a8e9ae1 100644 > --- a/gdb/aarch64-tdep.c > +++ b/gdb/aarch64-tdep.c > @@ -1911,6 +1911,24 @@ 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<aarch64_gdbarch_tdep> (arch); > + CORE_ADDR gcs_addr; > + > + enum 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 > @@ -3602,6 +3620,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. */ > @@ -3653,6 +3674,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); > } > } > > @@ -3811,6 +3838,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); > @@ -3907,20 +3940,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 0bee4b34845d..fd4d060412e5 100644 > --- a/gdb/linux-tdep.h > +++ b/gdb/linux-tdep.h > @@ -30,6 +30,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 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" Thanks. This is OK. Approved-By: Luis Machado <luis.machado@arm.com>
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c index 6bd9dfc47d81..63c6f6b76b92 100644 --- a/gdb/aarch64-linux-tdep.c +++ b/gdb/aarch64-linux-tdep.c @@ -2534,6 +2534,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<CORE_ADDR> +aarch64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache, + bool &shadow_stack_enabled) +{ + aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch); + shadow_stack_enabled = false; + + if (!tdep->has_gcs_linux ()) + return {}; + + uint64_t features_enabled; + enum 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. */ @@ -3106,6 +3132,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 0e9e6644dd0a..e21c4a8e9ae1 100644 --- a/gdb/aarch64-tdep.c +++ b/gdb/aarch64-tdep.c @@ -1911,6 +1911,24 @@ 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<aarch64_gdbarch_tdep> (arch); + CORE_ADDR gcs_addr; + + enum 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 @@ -3602,6 +3620,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. */ @@ -3653,6 +3674,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); } } @@ -3811,6 +3838,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); @@ -3907,20 +3940,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 0bee4b34845d..fd4d060412e5 100644 --- a/gdb/linux-tdep.h +++ b/gdb/linux-tdep.h @@ -30,6 +30,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 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"