]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb/aarch64: record/replay support for LRCPC3
authorEzra Sitorus <ezra.sitorus@arm.com>
Mon, 1 Jun 2026 22:36:46 +0000 (23:36 +0100)
committerEzra Sitorus <ezra.sitorus@arm.com>
Mon, 1 Jun 2026 22:36:46 +0000 (23:36 +0100)
FEAT_LRCPC3 introduces various load/store instructions with release
consistency for cases where ordering is required. This patch teaches GDB
to decode these instructions for recording and reversing.

The gdb.reverse/aarch64-lrcpc3.exp testcase verifies that the
instructions are recorded and correctly reversed. In particular, there
are some interesting cases to note:
* ldapur/stlur are SIMD instructions, but are not decoded in the simd
  function.
* There are writeback cases to cover too. These were taken from the
  binutils testcases: gas/testsuite/gas/aarch64/rcpc3.s.

The full testsuite was done on aarch64-none-linux-gnu without LRCPC3.
The gdb.arch and gdb.reverse tests were run on Shrinkwrap with LRCPC3
support.

Please note:
1) There is no support for LRCPC and LRCPC2 instructions
2) LRCPC3 is gated with +rcpc3 in GCC/binutils and LLVM.

Approved-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
gdb/aarch64-tdep.c
gdb/testsuite/gdb.reverse/aarch64-lrcpc3.c [new file with mode: 0644]
gdb/testsuite/gdb.reverse/aarch64-lrcpc3.exp [new file with mode: 0644]
gdb/testsuite/lib/gdb.exp

index 673815c2763f554a2a23fb6324581185c1de6d68..0cce4072ac7a7f3acf83396b198c445522b9c9c8 100644 (file)
@@ -5812,6 +5812,93 @@ aarch64_record_load_store (aarch64_insn_decode_record *aarch64_insn_r)
          aarch64_insn_r->reg_rec_count = 1;
        }
     }
+  /* LRCPC3 instructions.  This covers ldiapp/stilp, ldapur/stlur (FP/SIMD),
+     ldapr/stlr.  */
+  else if ((insn_bits24_27 & 0x0b) == 0x09 && insn_bits28_29 == 0x01
+          && insn_bits10_11 == 0x02 && !insn_bit21)
+    {
+      /* ldapur/stlur (FP/SIMD), ldapr/stlr.  We can differentiate between the
+        2 types by checking the vector flag.  */
+      if (insn_bit23 || vector_flag)
+       {
+         /* For the vector instruction, the offset comes from the imm9
+            bitfield, whereas the other can only take possible values from the
+            size bitfield.  */
+         int16_t imm9_off = sbits (aarch64_insn_r->aarch64_insn, 12, 20);
+         offset = vector_flag ? imm9_off : -(1 << size_bits);
+         uint32_t regnum_offset = vector_flag ? AARCH64_V0_REGNUM : 0;
+         if (ld_flag)
+           {
+             record_buf[0] = reg_rt + regnum_offset;
+             aarch64_insn_r->reg_rec_count = 1;
+             if (!vector_flag)
+               {
+                 /* The Rn register always has writeback in LRCPC3. This is
+                    not the case in LRCPC.  */
+                 record_buf[1] = reg_rn;
+                 aarch64_insn_r->reg_rec_count = 2;
+               }
+           }
+         else
+           {
+             regcache_raw_read_unsigned (aarch64_insn_r->regcache, reg_rn,
+                                         &address);
+             /* (vector_flag && insn_bit23) is the STLUR instruction with Q
+                register.  */
+             datasize = (vector_flag && insn_bit23) ? 128 : (8 << size_bits);
+             /* LRCPC3 adds STLR with a pre-indexed offset. There is another
+                STLR variant without offset but this has a different encoding.  */
+             if (!vector_flag)
+               {
+                 record_buf[0] = reg_rn;
+                 aarch64_insn_r->reg_rec_count = 1;
+               }
+             record_buf_mem[0] = datasize >> 3;
+             record_buf_mem[1] = address + offset;
+             aarch64_insn_r->mem_rec_count = 1;
+           }
+       }
+      else
+       {
+         /* ldiapp/stilp.  */
+         uint8_t opc2 = bits (aarch64_insn_r->aarch64_insn, 12, 15);
+         reg_rt2 = bits (aarch64_insn_r->aarch64_insn, 16, 20);
+         if (ld_flag)
+           {
+             record_buf[0] = reg_rt;
+             record_buf[1] = reg_rt2;
+             aarch64_insn_r->reg_rec_count = 2;
+
+             /* If the registers don't match and there's no offset then
+                there's WB.  */
+             if (reg_rn != reg_rt && reg_rn != reg_rt2 && opc2 == 0)
+               {
+                 record_buf[2] = reg_rn;
+                 aarch64_insn_r->reg_rec_count = 3;
+               }
+           }
+         else
+           {
+             datasize = 8 << size_bits;
+             regcache_raw_read_unsigned (aarch64_insn_r->regcache, reg_rn,
+                                         &address);
+             offset = (opc2 == 0) ? (2 << size_bits) : 0;
+             address -= offset;
+
+             record_buf_mem[0] = datasize >> 3;
+             record_buf_mem[1] = address;
+             record_buf_mem[2] = datasize >> 3;
+             record_buf_mem[3] = address + (datasize >> 3);
+             aarch64_insn_r->mem_rec_count = 2;
+
+             if (offset != 0)
+               {
+                 record_buf[0] = reg_rn;
+                 aarch64_insn_r->reg_rec_count = 1;
+               }
+           }
+       }
+    }
   /* Load/store register (register offset) instructions.  */
   else if ((insn_bits24_27 & 0x0b) == 0x08 && insn_bits28_29 == 0x03
           && insn_bits10_11 == 0x02 && insn_bit21)
diff --git a/gdb/testsuite/gdb.reverse/aarch64-lrcpc3.c b/gdb/testsuite/gdb.reverse/aarch64-lrcpc3.c
new file mode 100644 (file)
index 0000000..d61d323
--- /dev/null
@@ -0,0 +1,216 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2024-2026 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 <stdalign.h>
+#include <stdint.h>
+#include <string.h>
+
+#define INITIAL_STRING "This is just some string."
+#define BUF_SIZE sizeof (INITIAL_STRING)
+
+#define PREPARE_SRC_AND_PTR(OFFSET)                                       \
+  strcpy (src, INITIAL_STRING);                                               \
+  __asm__ volatile ("mov x21, %0"                                           \
+                   :                                                    \
+                   : "r"((uint64_t *)((uint8_t *)src + (OFFSET)))          \
+                   : "x21")
+
+#define PREPARE_GPR(OFFSET)                                          \
+  __asm__ volatile ("mov x19, #0\n" ::: "x19");                               \
+  __asm__ volatile ("mov x20, #0\n" ::: "x20");                               \
+  PREPARE_SRC_AND_PTR (OFFSET)
+
+#define PREPARE_VECTOR_REG(OFFSET)                                         \
+  __asm__ volatile ("movi v22.2d, #0\n" ::: "v22");                       \
+  PREPARE_SRC_AND_PTR (OFFSET)
+
+int
+main (void)
+{
+  alignas (16) char src[BUF_SIZE];
+
+  PREPARE_GPR (0);
+  /* Before ldiapp-0.  */
+  __asm__ volatile ("ldiapp x19, x20, [x21]\n" : : : "x19", "x20", "memory");
+  /* After ldiapp-0.  */
+
+  PREPARE_GPR (0);
+  /* Before ldiapp-1.  */
+  __asm__ volatile ("ldiapp w19, w20, [x21]\n" : : : "x19", "x20", "memory");
+  /* After ldiapp-1.  */
+
+  PREPARE_GPR (0);
+  /* Before ldiapp-2.  */
+  __asm__ volatile ("ldiapp x19, x20, [x21], #16\n"
+                   :
+                   :
+                   : "x19", "x20", "x21", "memory");
+  /* After ldiapp-2.  */
+
+  PREPARE_GPR (0);
+  /* Before ldiapp-3.  */
+  __asm__ volatile ("ldiapp w19, w20, [x21], #8\n"
+                   :
+                   :
+                   : "x19", "x20", "x21", "memory");
+  /* After ldiapp-3.  */
+  /* Register overlap between source and destination registers.  Since there is
+     no offset, writeback is disabled.  */
+
+  PREPARE_GPR (0);
+  /* Before ldiapp-4.  */
+  __asm__ volatile ("ldiapp x21, x20, [x21]\n" : : : "x20", "x21", "memory");
+  /* After ldiapp-4.  */
+
+  PREPARE_GPR (0);
+  /* Before ldiapp-5.  */
+  __asm__ volatile ("ldiapp w21, w20, [x21]\n" : : : "x20", "x21", "memory");
+  /* After ldiapp-5.  */
+
+  PREPARE_GPR (0);
+  /* Before stilp-0.  */
+  __asm__ volatile ("stilp x19, x20, [x21]\n" : : : "memory");
+  /* After stilp-0.  */
+
+  PREPARE_GPR (0);
+  /* Before stilp-1.  */
+  __asm__ volatile ("stilp w19, w20, [x21]\n" : : : "memory");
+  /* After stilp-1.  */
+
+  PREPARE_GPR (16);
+  /* Before stilp-2.  */
+  __asm__ volatile ("stilp x19, x20, [x21, #-16]!\n" : : : "x21", "memory");
+  /* After stilp-2.  */
+
+  PREPARE_GPR (8);
+  /* Before stilp-3.  */
+  __asm__ volatile ("stilp w19, w20, [x21, #-8]!\n" : : : "x21", "memory");
+  /* After stilp-3.  */
+  /* Register overlap.  Since there is no offset, writeback is disabled.  */
+
+  PREPARE_GPR (0);
+  /* Before stilp-4.  */
+  __asm__ volatile ("stilp x21, x20, [x21]\n" : : : "memory");
+  /* After stilp-4.  */
+
+  PREPARE_GPR (0);
+  /* Before stilp-5.  */
+  __asm__ volatile ("stilp w21, w20, [x21]\n" : : : "memory");
+  /* After stilp-5.  */
+
+  PREPARE_GPR (0);
+  /* Before ldapr-0.  */
+  __asm__ volatile ("ldapr x19, [x21], #8\n" : : : "x19", "x21", "memory");
+  /* After ldapr-0.  */
+
+  PREPARE_GPR (0);
+  /* Before ldapr-1.  */
+  __asm__ volatile ("ldapr w19, [x21], #4\n" : : : "x19", "x21", "memory");
+  /* After ldapr-1.  */
+
+  PREPARE_GPR (8);
+  /* Before stlr-0.  */
+  __asm__ volatile ("stlr x19, [x21, #-8]!\n" : : : "x21", "memory");
+  /* After stlr-0.  */
+
+  PREPARE_GPR (4);
+  /* Before stlr-1.  */
+  __asm__ volatile ("stlr w19, [x21, #-4]!\n" : : : "x21", "memory");
+  /* After stlr-1.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before ldap1-0.  */
+  __asm__ volatile ("ldap1 {v22.d}[0], [x21]\n" : : : "v22", "memory");
+  /* After ldap1-0.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before stl1-0.  */
+  __asm__ volatile ("stl1 {v22.d}[0], [x21]\n" : : : "memory");
+  /* After stl1-0.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before ldapur-0.  */
+  __asm__ volatile ("ldapur d22, [x21]\n" : : : "v22", "memory");
+  /* After ldapur-0.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before stlur-0.  */
+  __asm__ volatile ("stlur d22, [x21]\n" : : : "memory");
+  /* After stlur-0.  */
+
+  PREPARE_VECTOR_REG (256);
+  /* Before ldapur-1.  */
+  __asm__ volatile ("ldapur d22, [x21, #-256]\n" : : : "v22", "memory");
+  /* After ldapur-1.  */
+
+  PREPARE_VECTOR_REG (256);
+  /* Before stlur-1.  */
+  __asm__ volatile ("stlur d22, [x21, #-256]\n" : : : "memory");
+  /* After stlur-1.  */
+
+  PREPARE_VECTOR_REG (-255);
+  /* Before ldapur-2.  */
+  __asm__ volatile ("ldapur d22, [x21, #255]\n" : : : "v22", "memory");
+  /* After ldapur-2.  */
+
+  PREPARE_VECTOR_REG (-255);
+  /* Before stlur-2.  */
+  __asm__ volatile ("stlur d22, [x21, #255]\n" : : : "memory");
+  /* After stlur-2.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before ldapur-3.  */
+  __asm__ volatile ("ldapur h22, [x21]\n" : : : "v22", "memory");
+  /* After ldapur-3.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before stlur-3.  */
+  __asm__ volatile ("stlur h22, [x21]\n" : : : "memory");
+  /* After stlur-3.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before ldapur-4.  */
+  __asm__ volatile ("ldapur s22, [x21]\n" : : : "v22", "memory");
+  /* After ldapur-4.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before stlur-4.  */
+  __asm__ volatile ("stlur s22, [x21]\n" : : : "memory");
+  /* After stlur-4.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before ldapur-5.  */
+  __asm__ volatile ("ldapur d22, [x21]\n" : : : "v22", "memory");
+  /* After ldapur-5.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before stlur-5.  */
+  __asm__ volatile ("stlur d22, [x21]\n" : : : "memory");
+  /* After stlur-5.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before ldapur-6.  */
+  __asm__ volatile ("ldapur q22, [x21]\n" : : : "v22", "memory");
+  /* After ldapur-6.  */
+
+  PREPARE_VECTOR_REG (0);
+  /* Before stlur-6.  */
+  __asm__ volatile ("stlur q22, [x21]\n" : : : "memory");
+  /* After stlur-6.  */
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.reverse/aarch64-lrcpc3.exp b/gdb/testsuite/gdb.reverse/aarch64-lrcpc3.exp
new file mode 100644 (file)
index 0000000..b9e4814
--- /dev/null
@@ -0,0 +1,190 @@
+# Copyright 2024-2026 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 instruction record for AArch64 FEAT_LRCPC3 instructions.
+# Based on gdb.reverse/aarch64-mops.exp
+#
+# The basic flow of the record tests are:
+#    1) Stop before executing the instructions of interest.  Record
+#       the initial value of the registers that the instruction will
+#       change, i.e. the destination register.
+#    2) Execute the instructions.  Record the new value of the
+#       registers that changed.
+#    3) Reverse the direction of the execution and execute back to
+#       just before the instructions of interest.  Record the final
+#       value of the registers of interest.
+#    4) Check that the initial and new values of the registers are
+#       different, i.e. the instruction changed the registers as expected.
+#    5) Check that the initial and final values of the registers are
+#       the same, i.e. GDB record restored the registers to their
+#       original values.
+
+require allow_aarch64_lrcpc3_tests
+
+standard_testfile
+
+if {[prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+       [list debug additional_flags=-march=armv8-a+rcpc3]]} {
+    return
+}
+
+if {![runto_main]} {
+    return
+}
+
+gdb_test_no_output "record full"
+
+proc test_single_asm {name diff_reg diff_mem same_reg same_mem} {
+    with_test_prefix $name {
+
+       set before_seq [gdb_get_line_number "Before ${name}"]
+       set after_seq [gdb_get_line_number "After ${name}"]
+
+       set insn [lindex [split $name "-"] 0]
+
+       gdb_test "break $before_seq" \
+           "Breakpoint ${::decimal} at ${::hex}: file .*/aarch64-lrcpc3.c, line ${::decimal}\\." \
+           "$insn before instruction sequence"
+
+       gdb_continue_to_breakpoint "about to execute instruction sequence" \
+           [multi_line ".*/aarch64-lrcpc3.c:${::decimal}" \
+               "${::decimal}\[ \t\]+__asm__ volatile \\(\"${insn} .*\".*"]
+
+       # Depending on the compiler, the line number information may put GDB a few
+       # instructions before the beginning of the asm statement.
+       arrive_at_instruction $insn
+       # Add a breakpoint that we're sure is at the prologue instruction.
+       gdb_test "break *\$pc" \
+           "Breakpoint ${::decimal} at ${::hex}: file .*/aarch64-lrcpc3.c, line ${::decimal}\\." \
+           "break at prologue instruction"
+
+       # Record the initial memory and register values.
+       set regs [concat $diff_reg $same_reg]
+       set mem [concat $diff_mem $same_mem]
+       foreach r $regs {
+           set ${r}_initial [capture_command_output "info register $r" ""]
+       }
+       foreach m $mem {
+           set ${m}_initial [capture_command_output "x/x $m" ""]
+       }
+
+       gdb_test "break $after_seq" \
+           "Breakpoint ${::decimal} at ${::hex}: file .*/aarch64-lrcpc3.c, line ${::decimal}\\." \
+           "$insn after instruction sequence"
+       gdb_continue_to_breakpoint "executed instruction sequence" \
+           [multi_line ".*/aarch64-lrcpc3.c:${::decimal}" ".*"]
+
+       # Record the new memory and register values.
+       foreach r $regs {
+           set ${r}_new [capture_command_output "info register $r" ""]
+       }
+       foreach m $mem {
+           set ${m}_new [capture_command_output "x/x $m" ""]
+       }
+
+       # Execute in reverse to before the instruction sequence.
+       gdb_test_no_output "set exec-direction reverse"
+
+       gdb_continue_to_breakpoint "reversed execution of instruction sequence" \
+           [multi_line ".*/aarch64-lrcpc3.c:${::decimal}" \
+               "${::decimal}\[ \t\]+__asm__ volatile \\(\"${insn} .*\".*"]
+
+       # Record the final memory and register values.
+       foreach r $regs {
+           set ${r}_final [capture_command_output "info register $r" ""]
+       }
+       foreach m $mem {
+           set ${m}_final [capture_command_output "x/x $m" ""]
+       }
+
+       foreach v [concat $same_reg $same_mem] {
+           gdb_assert ![string compare [set ${v}_initial] [set ${v}_new]] \
+               "check $v initial value versus $v new value"
+           gdb_assert ![string compare [set ${v}_initial] [set ${v}_final]] \
+               "check $v initial value versus $v final value"
+       }
+
+       foreach v [concat $diff_reg $diff_mem] {
+           gdb_assert [string compare [set ${v}_initial] [set ${v}_new]] \
+               "check $v initial value versus $v new value"
+           gdb_assert ![string compare [set ${v}_initial] [set ${v}_final]] \
+               "check $v initial value versus $v final value"
+       }
+
+       # Restore forward execution and go to end of recording.
+       gdb_test_no_output "set exec-direction forward"
+       gdb_test "record goto end" \
+           [multi_line \
+               "Go forward to insn number ${::decimal}" \
+               "#0  main \\(\\) at .*/aarch64-lrcpc3.c:${::decimal}" \
+               ".*"]
+    }
+}
+
+set ldiapp_cases {
+       { ldiapp-0 { x19 x20     } { } { x21 } { src } }
+       { ldiapp-1 { w19 w20     } { } { x21 } { src } }
+       { ldiapp-2 { x19 x20 x21 } { } {     } { src } }
+       { ldiapp-3 { w19 w20 x21 } { } {     } { src } }
+       { ldiapp-4 { x21 x20     } { } {     } { src } }
+       { ldiapp-5 { w21 w20     } { } {     } { src } }
+}
+
+set stilp_cases {
+       { stilp-0  {     } { src } { x19 x20 x21 } { } }
+       { stilp-1  {     } { src } { w19 w20 x21 } { } }
+       { stilp-2  { x21 } { src } { x19 x20     } { } }
+       { stilp-3  { x21 } { src } { x19 x20     } { } }
+       { stilp-4  {     } { src } { x20     x21 } { } }
+       { stilp-5  {     } { src } { w20     x21 } { } }
+}
+
+set ldapr_stlr_cases {
+       { ldapr-0  { x19 x21 } {     } {     } { src } }
+       { ldapr-1  { w19 x21 } {     } {     } { src } }
+       { stlr-0   {     x21 } { src } { x19 } {     } }
+       { stlr-1   {     x21 } { src } { w19 } {     } }
+}
+
+set ldap1_stl1_cases {
+       { ldap1-0  { v22 } {     } { x21 } { src } }
+       { stl1-0   {     } { src } { v22 } {     } }
+}
+
+set ldapur_stlur_cases {
+       { ldapur-0 { v22 } {     } { x21     } { src } }
+       { stlur-0  {     } { src } { x21 v22 } {     } }
+       { ldapur-1 { v22 } {     } {     x21 } { src } }
+       { stlur-1  {     } { src } { x21 v22 } {     } }
+       { ldapur-2 { v22 } {     } {     x21 } { src } }
+       { stlur-2  {     } { src } { x21 v22 } {     } }
+       { ldapur-3 { v22 } {     } {     x21 } { src } }
+       { stlur-3  {     } { src } { x21 v22 } {     } }
+       { ldapur-4 { v22 } {     } {     x21 } { src } }
+       { stlur-4  {     } { src } { x21 v22 } {     } }
+       { ldapur-5 { v22 } {     } {     x21 } { src } }
+       { stlur-5  {     } { src } { x21 v22 } {     } }
+       { ldapur-6 { v22 } {     } {     x21 } { src } }
+       { stlur-6  {     } { src } { x21 v22 } {     } }
+}
+
+set all_cases [concat \
+    $ldiapp_cases $stilp_cases $ldapr_stlr_cases \
+    $ldap1_stl1_cases $ldapur_stlur_cases]
+
+foreach c $all_cases {
+    lassign $c name diff_reg diff_mem same_reg same_mem
+    test_single_asm $name $diff_reg $diff_mem $same_reg $same_mem
+}
index 96ef9e952a8c635e6296503db58151f07ddc5c45..c87876664f02936bcefe1d2785e94c370e9f8043 100644 (file)
@@ -5332,6 +5332,65 @@ gdb_caching_proc allow_aarch64_fpmr_tests {} {
     return $allow_fpmr_tests
 }
 
+# Run a test on the target to see if it supports AArch64 LRCPC3 (Load-Acquire
+# RCpc instructions) extensions.  Return 1 if so, 0 if it does not.  Note this
+# causes a restart of GDB.
+
+gdb_caching_proc allow_aarch64_lrcpc3_tests {} {
+    global srcdir subdir gdb_prompt inferior_exited_re
+
+    set me "allow_aarch64_lrcpc3_tests"
+
+    if { ![is_aarch64_target] } {
+       return 0
+    }
+
+    # Take the opportunity to check whether the toolchain knows about LRCPC3.
+    set compile_flags "{additional_flags=-march=armv8-a+rcpc3}"
+
+    # Compile a program that tests the LRCPC3 feature.
+    set src {
+       #include <stdbool.h>
+       #include <sys/auxv.h>
+
+       /* Feature check for LRCPC3.  */
+       #ifndef HWCAP2_LRCPC3
+       #define HWCAP2_LRCPC3 (1ULL << 46)
+       #endif
+
+       int main (void) {
+           bool lrcpc3_supported = getauxval (AT_HWCAP2) & HWCAP2_LRCPC3;
+
+           /* Return success if LRCPC3 is supported.  */
+           return !lrcpc3_supported;
+       }
+    }
+
+    if {![gdb_simple_compile $me $src executable $compile_flags]} {
+       return 0
+    }
+
+    # Compilation succeeded so now run it via gdb.
+    clean_restart
+    gdb_load $obj
+    gdb_run_cmd
+    gdb_expect {
+    -re ".*$inferior_exited_re normally.*${gdb_prompt} $" {
+           verbose -log "\n$me: lrcpc3 support detected"
+           set allow_lrcpc3_tests 1
+       }
+       -re ".*$inferior_exited_re with code 01.*${gdb_prompt} $" {
+           verbose -log "\n$me: lrcpc3 support not detected"
+           set allow_lrcpc3_tests 0
+       }
+    }
+    gdb_exit
+    remote_file build delete $obj
+
+    verbose "$me:  returning $allow_lrcpc3_tests" 2
+    return $allow_lrcpc3_tests
+}
+
 # Run a test on the target to see if it supports the AArch64 CSSC feature.
 # Return 1 if so, 0 if it does not.  Note this causes a restart of GDB.