diff mbox series

[v3,5/5] gdb/testsuite: Add gdb.reverse/aarch64-mops.exp

Message ID 20240510052408.2173579-6-thiago.bauermann@linaro.org
State New
Headers show
Series Add support for AArch64 MOPS instructions | expand

Commit Message

Thiago Jung Bauermann May 10, 2024, 5:24 a.m. UTC
The testcase verifies that MOPS instructions are recorded and correctly
reversed.  Not all variants of the copy and set instructions are tested,
since there are many and the record and replay target processes them in
the same way.
---
 gdb/testsuite/gdb.reverse/aarch64-mops.c   |  71 +++++++++
 gdb/testsuite/gdb.reverse/aarch64-mops.exp | 171 +++++++++++++++++++++
 2 files changed, 242 insertions(+)
 create mode 100644 gdb/testsuite/gdb.reverse/aarch64-mops.c
 create mode 100644 gdb/testsuite/gdb.reverse/aarch64-mops.exp

No change since v1.

Comments

Luis Machado May 10, 2024, 1:08 p.m. UTC | #1
Thanks. Looks OK to me.

On 5/10/24 06:24, Thiago Jung Bauermann wrote:
> The testcase verifies that MOPS instructions are recorded and correctly
> reversed.  Not all variants of the copy and set instructions are tested,
> since there are many and the record and replay target processes them in
> the same way.
> ---
>  gdb/testsuite/gdb.reverse/aarch64-mops.c   |  71 +++++++++
>  gdb/testsuite/gdb.reverse/aarch64-mops.exp | 171 +++++++++++++++++++++
>  2 files changed, 242 insertions(+)
>  create mode 100644 gdb/testsuite/gdb.reverse/aarch64-mops.c
>  create mode 100644 gdb/testsuite/gdb.reverse/aarch64-mops.exp
> 
> No change since v1.
> 
> diff --git a/gdb/testsuite/gdb.reverse/aarch64-mops.c b/gdb/testsuite/gdb.reverse/aarch64-mops.c
> new file mode 100644
> index 000000000000..513f324b9dd6
> --- /dev/null
> +++ b/gdb/testsuite/gdb.reverse/aarch64-mops.c
> @@ -0,0 +1,71 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2024 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/>.  */
> +
> +#define TEST_STRING "Just a test string."
> +#define BUF_SIZE sizeof(TEST_STRING)
> +
> +int
> +main (void)
> +{
> +  char dest[BUF_SIZE];
> +  char source[BUF_SIZE] = TEST_STRING;
> +  register char *p asm ("x19");
> +  register char *q asm ("x20");
> +  register long size asm ("x21");
> +  register long zero asm ("x22");
> +
> +  p = dest;
> +  size = BUF_SIZE;
> +  zero = 0;
> +  /* Before setp.  */
> +  /* memset implemented in MOPS instructions.  */
> +  __asm__ volatile ("setp [%0]!, %1!, %2\n\t"
> +                    "setm [%0]!, %1!, %2\n\t"
> +                    "sete [%0]!, %1!, %2\n\t"
> +                    : "+&r"(p), "+&r"(size)
> +                    : "r"(zero)
> +                    : "memory");
> +
> +  /* After sete.  */
> +  p = dest;
> +  q = source;
> +  size = BUF_SIZE;
> +  /* Before cpyp.  */
> +  /* memmove implemented in MOPS instructions.  */
> +  __asm__ volatile ("cpyp   [%0]!, [%1]!, %2!\n\t"
> +		    "cpym   [%0]!, [%1]!, %2!\n\t"
> +		    "cpye   [%0]!, [%1]!, %2!\n\t"
> +		    : "+&r" (p), "+&r" (q), "+&r" (size)
> +		    :
> +		    : "memory");
> +  /* After cpye.  */
> +  p = dest;
> +  q = source;
> +  size = BUF_SIZE;
> +  /* Before cpyfp.  */
> +  /* memcpy implemented in MOPS instructions.  */
> +  __asm__ volatile ("cpyfp   [%0]!, [%1]!, %2!\n\t"
> +		    "cpyfm   [%0]!, [%1]!, %2!\n\t"
> +		    "cpyfe   [%0]!, [%1]!, %2!\n\t"
> +		    : "+&r" (p), "+&r" (q), "+&r" (size)
> +		    :
> +		    : "memory");
> +  /* After cpyfe.  */
> +  p = dest;
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.reverse/aarch64-mops.exp b/gdb/testsuite/gdb.reverse/aarch64-mops.exp
> new file mode 100644
> index 000000000000..f9c1257e0b11
> --- /dev/null
> +++ b/gdb/testsuite/gdb.reverse/aarch64-mops.exp
> @@ -0,0 +1,171 @@
> +# Copyright 2024 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_MOPS instructions.
> +# Based on gdb.reverse/ppc_record_test_isa_3_1.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_mops_tests
> +
> +standard_testfile
> +
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
> +	  [list debug additional_flags=-march=armv9.3-a]] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +gdb_test_no_output "record full"
> +
> +proc do_test { insn_prefix } {
> +    global decimal hex
> +
> +    set before_seq [gdb_get_line_number "Before ${insn_prefix}p"]
> +    set after_seq [gdb_get_line_number "After ${insn_prefix}e"]
> +
> +    with_test_prefix $insn_prefix {
> +	gdb_test "break $before_seq" \
> +	    "Breakpoint $decimal at $hex: file .*/aarch64-mops.c, line $decimal\\." \
> +	    "break before instruction sequence"
> +	gdb_test "continue" \
> +	    [multi_line \
> +		 "Continuing\\." \
> +		 "" \
> +		 "Breakpoint $decimal, main \\(\\) at .*/aarch64-mops.c:$decimal" \
> +		 "$decimal\[ \t\]+__asm__ volatile \\(\"${insn_prefix}p \[^\r\n\]+\""] \
> +	    "about to execute instruction sequence"
> +
> +	# Record the initial register values.
> +	set x19_initial [capture_command_output "info register x19" ""]
> +	set x21_initial [capture_command_output "info register x21" ""]
> +
> +	# The set instructions use the ZERO variable, but not Q, and the other
> +	# instructions are the opposite.
> +	if {[string compare $insn_prefix "set"] == 0} {
> +	    set x22_initial [capture_command_output "info register x22" ""]
> +	} else {
> +	    set x20_initial [capture_command_output "info register x20" ""]
> +	}
> +
> +	gdb_test "break $after_seq" \
> +	    "Breakpoint $decimal at $hex: file .*/aarch64-mops.c, line $decimal\\." \
> +	    "break after instruction sequence"
> +	gdb_test "continue" \
> +	    [multi_line \
> +		 "Continuing\\." \
> +		 "" \
> +		 "Breakpoint $decimal, main \\(\\) at .*/aarch64-mops.c:$decimal" \
> +		 "$decimal\[ \t\]+p = dest;"] \
> +	    "executed instruction sequence"
> +
> +	# Record the new register values.
> +	set x19_new [capture_command_output "info register x19" ""]
> +	set x21_new [capture_command_output "info register x21" ""]
> +
> +	if {[string compare $insn_prefix "set"] == 0} {
> +	    set x22_new [capture_command_output "info register x22" ""]
> +	} else {
> +	    set x20_new [capture_command_output "info register x20" ""]
> +	}
> +
> +	# Execute in reverse to before the instruction sequence.
> +	gdb_test_no_output "set exec-direction reverse"
> +
> +	gdb_test "continue" \
> +	    [multi_line \
> +		 "Continuing\\." \
> +		 "" \
> +		 "Breakpoint $decimal, main \\(\\) at .*/aarch64-mops.c:$decimal" \
> +		 "$decimal\[ \t\]+__asm__ volatile \\(\"${insn_prefix}p \[^\r\n\]+\""] \
> +	    "reversed execution of instruction sequence"
> +
> +	# Record the final register values.
> +	set x19_final [capture_command_output "info register x19" ""]
> +	set x21_final [capture_command_output "info register x21" ""]
> +
> +	if {[string compare $insn_prefix "set"] == 0} {
> +	    set x22_final [capture_command_output "info register x22" ""]
> +	} else {
> +	    set x20_final [capture_command_output "info register x20" ""]
> +	}
> +
> +	# Check initial and new values of x19 are different.
> +	gdb_assert [string compare $x19_initial $x19_new] \
> +	    "check x19 initial value versus x19 new value"
> +
> +	# Check initial and new values of x21 are different.
> +	gdb_assert [string compare $x21_initial $x21_new] \
> +	    "check x21 initial value versus x21 new value"
> +
> +	if {[string compare $insn_prefix "set"] == 0} {
> +	    # Check initial and new values of x22 are the same.
> +	    # The register with the value to set shouldn't change.
> +	    gdb_assert ![string compare $x22_initial $x22_new] \
> +		"check x22 initial value versus x22 new value"
> +	} else {
> +	    # Check initial and new values of x20 are different.
> +	    gdb_assert [string compare $x20_initial $x20_new] \
> +		"check x20 initial value versus x20 new value"
> +	}
> +
> +	# Check initial and final values of x19 are the same.
> +	gdb_assert ![string compare $x19_initial $x19_final] \
> +	    "check x19 initial value versus x19 final value"
> +
> +	# Check initial and final values of x21 are the same.
> +	gdb_assert ![string compare $x21_initial $x21_final] \
> +	    "check x21 initial value versus x21 final value"
> +
> +	if {[string compare $insn_prefix "set"] == 0} {
> +	    # Check initial and final values of x22 are the same.
> +	    # The register with the value to set shouldn't change.
> +	    gdb_assert ![string compare $x22_initial $x22_final] \
> +		"check x22 initial value versus x22 final value"
> +	} else {
> +	    # Check initial and final values of x20 are the same.
> +	    gdb_assert ![string compare $x20_initial $x20_final] \
> +		"check x20 initial value versus x20 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-mops.c:$decimal" \
> +		"$decimal\[ \t\]+p = dest;"]
> +    }
> +}
> +
> +do_test "set"
> +do_test "cpy"
> +do_test "cpyf"
Guinevere Larsen May 10, 2024, 5:26 p.m. UTC | #2
On 5/10/24 02:24, Thiago Jung Bauermann wrote:
> The testcase verifies that MOPS instructions are recorded and correctly
> reversed.  Not all variants of the copy and set instructions are tested,
> since there are many and the record and replay target processes them in
> the same way.

Hi!

Thanks for adding this test! I agree that, if the record backend treats 
all instructions the same, there's no need to test them all.

As I mentioned in my reply to v2, I'd prefer if this was squashed to 
commit 4, but if you don't end up sending a new series, or feel strongly 
that they shouldn't be together, I'm ok with separating the commits.

I have a couple questions inlined.

> ---
>   gdb/testsuite/gdb.reverse/aarch64-mops.c   |  71 +++++++++
>   gdb/testsuite/gdb.reverse/aarch64-mops.exp | 171 +++++++++++++++++++++
>   2 files changed, 242 insertions(+)
>   create mode 100644 gdb/testsuite/gdb.reverse/aarch64-mops.c
>   create mode 100644 gdb/testsuite/gdb.reverse/aarch64-mops.exp
>
> No change since v1.
>
> diff --git a/gdb/testsuite/gdb.reverse/aarch64-mops.c b/gdb/testsuite/gdb.reverse/aarch64-mops.c
> new file mode 100644
> index 000000000000..513f324b9dd6
> --- /dev/null
> +++ b/gdb/testsuite/gdb.reverse/aarch64-mops.c
> @@ -0,0 +1,71 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2024 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/>.  */
> +
> +#define TEST_STRING "Just a test string."
> +#define BUF_SIZE sizeof(TEST_STRING)
> +
> +int
> +main (void)
> +{
> +  char dest[BUF_SIZE];
> +  char source[BUF_SIZE] = TEST_STRING;
> +  register char *p asm ("x19");
> +  register char *q asm ("x20");
> +  register long size asm ("x21");
> +  register long zero asm ("x22");

I remember that its not always easy to guarantee that a variable will 
end up in a register, so this has me slightly confused. Is this 
portable? Have you/could you double check that clang also does what you 
expect?

If it isn't portable, please add the gcc requirement, so we don't get 
new failures in clang testing :)

> +
> +  p = dest;
> +  size = BUF_SIZE;
> +  zero = 0;
> +  /* Before setp.  */
> +  /* memset implemented in MOPS instructions.  */
> +  __asm__ volatile ("setp [%0]!, %1!, %2\n\t"
> +                    "setm [%0]!, %1!, %2\n\t"
> +                    "sete [%0]!, %1!, %2\n\t"
> +                    : "+&r"(p), "+&r"(size)
> +                    : "r"(zero)
> +                    : "memory");
> +
> +  /* After sete.  */
> +  p = dest;
> +  q = source;
> +  size = BUF_SIZE;
> +  /* Before cpyp.  */
> +  /* memmove implemented in MOPS instructions.  */
> +  __asm__ volatile ("cpyp   [%0]!, [%1]!, %2!\n\t"
> +		    "cpym   [%0]!, [%1]!, %2!\n\t"
> +		    "cpye   [%0]!, [%1]!, %2!\n\t"
> +		    : "+&r" (p), "+&r" (q), "+&r" (size)
> +		    :
> +		    : "memory");
> +  /* After cpye.  */
> +  p = dest;
> +  q = source;
> +  size = BUF_SIZE;
> +  /* Before cpyfp.  */
> +  /* memcpy implemented in MOPS instructions.  */
> +  __asm__ volatile ("cpyfp   [%0]!, [%1]!, %2!\n\t"
> +		    "cpyfm   [%0]!, [%1]!, %2!\n\t"
> +		    "cpyfe   [%0]!, [%1]!, %2!\n\t"
> +		    : "+&r" (p), "+&r" (q), "+&r" (size)
> +		    :
> +		    : "memory");
> +  /* After cpyfe.  */
> +  p = dest;
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.reverse/aarch64-mops.exp b/gdb/testsuite/gdb.reverse/aarch64-mops.exp
> new file mode 100644
> index 000000000000..f9c1257e0b11
> --- /dev/null
> +++ b/gdb/testsuite/gdb.reverse/aarch64-mops.exp
> @@ -0,0 +1,171 @@
> +# Copyright 2024 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_MOPS instructions.
> +# Based on gdb.reverse/ppc_record_test_isa_3_1.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_mops_tests
> +
> +standard_testfile
> +
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
> +	  [list debug additional_flags=-march=armv9.3-a]] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +gdb_test_no_output "record full"
> +
> +proc do_test { insn_prefix } {

more of a personal preference, maybe, but you could use:

     foreach_with_prefix insn_prefix {"set" "cpy" "cpyf"}

> +    global decimal hex
> +
> +    set before_seq [gdb_get_line_number "Before ${insn_prefix}p"]
> +    set after_seq [gdb_get_line_number "After ${insn_prefix}e"]
> +
> +    with_test_prefix $insn_prefix {
> +	gdb_test "break $before_seq" \
> +	    "Breakpoint $decimal at $hex: file .*/aarch64-mops.c, line $decimal\\." \
> +	    "break before instruction sequence"
> +	gdb_test "continue" \
> +	    [multi_line \
> +		 "Continuing\\." \
> +		 "" \
> +		 "Breakpoint $decimal, main \\(\\) at .*/aarch64-mops.c:$decimal" \
> +		 "$decimal\[ \t\]+__asm__ volatile \\(\"${insn_prefix}p \[^\r\n\]+\""] \
> +	    "about to execute instruction sequence"

Why did you choose to not use gdb_continue_to_breakpoint in here?

Similar question to all other uses of "continue"
Thiago Jung Bauermann May 23, 2024, 1:55 a.m. UTC | #3
Hello Guinevere,

Thank you for your review!

Guinevere Larsen <blarsen@redhat.com> writes:

> On 5/10/24 02:24, Thiago Jung Bauermann wrote:
>> The testcase verifies that MOPS instructions are recorded and correctly
>> reversed.  Not all variants of the copy and set instructions are tested,
>> since there are many and the record and replay target processes them in
>> the same way.
>
> Hi!
>
> Thanks for adding this test! I agree that, if the record backend treats all instructions
> the same, there's no need to test them all.

Nice, thanks for confirming.

> As I mentioned in my reply to v2, I'd prefer if this was squashed to commit 4, but if you
> don't end up sending a new series, or feel strongly that they shouldn't be together, I'm
> ok with separating the commits.

No problem, makes sense to me. Though I assume you mean commit 2. Done
for v4.

>> +int
>> +main (void)
>> +{
>> +  char dest[BUF_SIZE];
>> +  char source[BUF_SIZE] = TEST_STRING;
>> +  register char *p asm ("x19");
>> +  register char *q asm ("x20");
>> +  register long size asm ("x21");
>> +  register long zero asm ("x22");
>
> I remember that its not always easy to guarantee that a variable will end up in a
> register, so this has me slightly confused. Is this portable? Have you/could you double
> check that clang also does what you expect?
>
> If it isn't portable, please add the gcc requirement, so we don't get new failures in
> clang testing :)

That's a good point, thanks for bringing it up. This is a GNU C
extension, documented here:

https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html

And it's only supported for one specific case:

 "The only supported use for this feature is to specify registers for
  input and output operands when calling Extended asm (see Extended Asm
  - Assembler Instructions with C Expression Operands)."

which is how I'm using it in the testcase.

This extension isn't listed in Clang's list of supported extensions
here:

https://clang.llvm.org/docs/LanguageExtensions.html

Though that page says:

 "This document describes the language extensions provided by Clang. In
  addition to the language extensions listed here, Clang aims to support
  a broad range of GCC extensions. Please see the GCC manual for more
  information on these extensions."

I checked and Clang does what I expect and doesn't warn about it, so it
is indeed supported.

That said, the line numbers it generates for the asm statement are a bit
strange and the testcase ends up setting a breakpoint a few instructions
before the prologue instruction it is aiming for. To make the testcase
support Clang, I had to make it check which instruction GDB is stopped
at and stepi until it reaches the intended instruction.

>> +require allow_aarch64_mops_tests
>> +
>> +standard_testfile
>> +
>> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
>> +	  [list debug additional_flags=-march=armv9.3-a]] } {
>> +    return -1
>> +}
>> +
>> +if ![runto_main] {
>> +    return -1
>> +}
>> +
>> +gdb_test_no_output "record full"
>> +
>> +proc do_test { insn_prefix } {
>
> more of a personal preference, maybe, but you could use:
>
>     foreach_with_prefix insn_prefix {"set" "cpy" "cpyf"}

I agree it is better, thanks for the suggestion. Done in v4.

>> +    global decimal hex
>> +
>> +    set before_seq [gdb_get_line_number "Before ${insn_prefix}p"]
>> +    set after_seq [gdb_get_line_number "After ${insn_prefix}e"]
>> +
>> +    with_test_prefix $insn_prefix {
>> +	gdb_test "break $before_seq" \
>> + "Breakpoint $decimal at $hex: file .*/aarch64-mops.c, line $decimal\\." \
>> +	    "break before instruction sequence"
>> +	gdb_test "continue" \
>> +	    [multi_line \
>> +		 "Continuing\\." \
>> +		 "" \
>> +		 "Breakpoint $decimal, main \\(\\) at .*/aarch64-mops.c:$decimal" \
>> + "$decimal\[ \t\]+__asm__ volatile \\(\"${insn_prefix}p \[^\r\n\]+\""] \
>> +	    "about to execute instruction sequence"
>
> Why did you choose to not use gdb_continue_to_breakpoint in here?
>
> Similar question to all other uses of "continue"

I wasn't aware that with gdb_continue_to_breakpoint I was able to
specify the location pattern that the test should match. I'm using it
now in v4, thanks for the suggestion.
diff mbox series

Patch

diff --git a/gdb/testsuite/gdb.reverse/aarch64-mops.c b/gdb/testsuite/gdb.reverse/aarch64-mops.c
new file mode 100644
index 000000000000..513f324b9dd6
--- /dev/null
+++ b/gdb/testsuite/gdb.reverse/aarch64-mops.c
@@ -0,0 +1,71 @@ 
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2024 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/>.  */
+
+#define TEST_STRING "Just a test string."
+#define BUF_SIZE sizeof(TEST_STRING)
+
+int
+main (void)
+{
+  char dest[BUF_SIZE];
+  char source[BUF_SIZE] = TEST_STRING;
+  register char *p asm ("x19");
+  register char *q asm ("x20");
+  register long size asm ("x21");
+  register long zero asm ("x22");
+
+  p = dest;
+  size = BUF_SIZE;
+  zero = 0;
+  /* Before setp.  */
+  /* memset implemented in MOPS instructions.  */
+  __asm__ volatile ("setp [%0]!, %1!, %2\n\t"
+                    "setm [%0]!, %1!, %2\n\t"
+                    "sete [%0]!, %1!, %2\n\t"
+                    : "+&r"(p), "+&r"(size)
+                    : "r"(zero)
+                    : "memory");
+
+  /* After sete.  */
+  p = dest;
+  q = source;
+  size = BUF_SIZE;
+  /* Before cpyp.  */
+  /* memmove implemented in MOPS instructions.  */
+  __asm__ volatile ("cpyp   [%0]!, [%1]!, %2!\n\t"
+		    "cpym   [%0]!, [%1]!, %2!\n\t"
+		    "cpye   [%0]!, [%1]!, %2!\n\t"
+		    : "+&r" (p), "+&r" (q), "+&r" (size)
+		    :
+		    : "memory");
+  /* After cpye.  */
+  p = dest;
+  q = source;
+  size = BUF_SIZE;
+  /* Before cpyfp.  */
+  /* memcpy implemented in MOPS instructions.  */
+  __asm__ volatile ("cpyfp   [%0]!, [%1]!, %2!\n\t"
+		    "cpyfm   [%0]!, [%1]!, %2!\n\t"
+		    "cpyfe   [%0]!, [%1]!, %2!\n\t"
+		    : "+&r" (p), "+&r" (q), "+&r" (size)
+		    :
+		    : "memory");
+  /* After cpyfe.  */
+  p = dest;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.reverse/aarch64-mops.exp b/gdb/testsuite/gdb.reverse/aarch64-mops.exp
new file mode 100644
index 000000000000..f9c1257e0b11
--- /dev/null
+++ b/gdb/testsuite/gdb.reverse/aarch64-mops.exp
@@ -0,0 +1,171 @@ 
+# Copyright 2024 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_MOPS instructions.
+# Based on gdb.reverse/ppc_record_test_isa_3_1.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_mops_tests
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+	  [list debug additional_flags=-march=armv9.3-a]] } {
+    return -1
+}
+
+if ![runto_main] {
+    return -1
+}
+
+gdb_test_no_output "record full"
+
+proc do_test { insn_prefix } {
+    global decimal hex
+
+    set before_seq [gdb_get_line_number "Before ${insn_prefix}p"]
+    set after_seq [gdb_get_line_number "After ${insn_prefix}e"]
+
+    with_test_prefix $insn_prefix {
+	gdb_test "break $before_seq" \
+	    "Breakpoint $decimal at $hex: file .*/aarch64-mops.c, line $decimal\\." \
+	    "break before instruction sequence"
+	gdb_test "continue" \
+	    [multi_line \
+		 "Continuing\\." \
+		 "" \
+		 "Breakpoint $decimal, main \\(\\) at .*/aarch64-mops.c:$decimal" \
+		 "$decimal\[ \t\]+__asm__ volatile \\(\"${insn_prefix}p \[^\r\n\]+\""] \
+	    "about to execute instruction sequence"
+
+	# Record the initial register values.
+	set x19_initial [capture_command_output "info register x19" ""]
+	set x21_initial [capture_command_output "info register x21" ""]
+
+	# The set instructions use the ZERO variable, but not Q, and the other
+	# instructions are the opposite.
+	if {[string compare $insn_prefix "set"] == 0} {
+	    set x22_initial [capture_command_output "info register x22" ""]
+	} else {
+	    set x20_initial [capture_command_output "info register x20" ""]
+	}
+
+	gdb_test "break $after_seq" \
+	    "Breakpoint $decimal at $hex: file .*/aarch64-mops.c, line $decimal\\." \
+	    "break after instruction sequence"
+	gdb_test "continue" \
+	    [multi_line \
+		 "Continuing\\." \
+		 "" \
+		 "Breakpoint $decimal, main \\(\\) at .*/aarch64-mops.c:$decimal" \
+		 "$decimal\[ \t\]+p = dest;"] \
+	    "executed instruction sequence"
+
+	# Record the new register values.
+	set x19_new [capture_command_output "info register x19" ""]
+	set x21_new [capture_command_output "info register x21" ""]
+
+	if {[string compare $insn_prefix "set"] == 0} {
+	    set x22_new [capture_command_output "info register x22" ""]
+	} else {
+	    set x20_new [capture_command_output "info register x20" ""]
+	}
+
+	# Execute in reverse to before the instruction sequence.
+	gdb_test_no_output "set exec-direction reverse"
+
+	gdb_test "continue" \
+	    [multi_line \
+		 "Continuing\\." \
+		 "" \
+		 "Breakpoint $decimal, main \\(\\) at .*/aarch64-mops.c:$decimal" \
+		 "$decimal\[ \t\]+__asm__ volatile \\(\"${insn_prefix}p \[^\r\n\]+\""] \
+	    "reversed execution of instruction sequence"
+
+	# Record the final register values.
+	set x19_final [capture_command_output "info register x19" ""]
+	set x21_final [capture_command_output "info register x21" ""]
+
+	if {[string compare $insn_prefix "set"] == 0} {
+	    set x22_final [capture_command_output "info register x22" ""]
+	} else {
+	    set x20_final [capture_command_output "info register x20" ""]
+	}
+
+	# Check initial and new values of x19 are different.
+	gdb_assert [string compare $x19_initial $x19_new] \
+	    "check x19 initial value versus x19 new value"
+
+	# Check initial and new values of x21 are different.
+	gdb_assert [string compare $x21_initial $x21_new] \
+	    "check x21 initial value versus x21 new value"
+
+	if {[string compare $insn_prefix "set"] == 0} {
+	    # Check initial and new values of x22 are the same.
+	    # The register with the value to set shouldn't change.
+	    gdb_assert ![string compare $x22_initial $x22_new] \
+		"check x22 initial value versus x22 new value"
+	} else {
+	    # Check initial and new values of x20 are different.
+	    gdb_assert [string compare $x20_initial $x20_new] \
+		"check x20 initial value versus x20 new value"
+	}
+
+	# Check initial and final values of x19 are the same.
+	gdb_assert ![string compare $x19_initial $x19_final] \
+	    "check x19 initial value versus x19 final value"
+
+	# Check initial and final values of x21 are the same.
+	gdb_assert ![string compare $x21_initial $x21_final] \
+	    "check x21 initial value versus x21 final value"
+
+	if {[string compare $insn_prefix "set"] == 0} {
+	    # Check initial and final values of x22 are the same.
+	    # The register with the value to set shouldn't change.
+	    gdb_assert ![string compare $x22_initial $x22_final] \
+		"check x22 initial value versus x22 final value"
+	} else {
+	    # Check initial and final values of x20 are the same.
+	    gdb_assert ![string compare $x20_initial $x20_final] \
+		"check x20 initial value versus x20 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-mops.c:$decimal" \
+		"$decimal\[ \t\]+p = dest;"]
+    }
+}
+
+do_test "set"
+do_test "cpy"
+do_test "cpyf"