[PUSHED] arc: Determine a branch target of DBNZ correctly

Message ID 20240221131721.15430-1-list+gdb@vahedi.org
State New
Headers
Series [PUSHED] arc: Determine a branch target of DBNZ correctly |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-arm warning Patch is already merged
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 warning Patch is already merged

Commit Message

Shahab Vahedi Feb. 21, 2024, 1:17 p.m. UTC
  From: Yuriy Kolerov <kolerov93@gmail.com>

DBNZ instruction was moved from BRANCH class to a separate one - DBNZ.
Thus, it must be processed separately in arc_insn_get_branch_target
to correctly determine an offset for a possible branch.

The testsuite for DBNZ instruction verifies these cases:

     1. Check that dbnz does not branch and falls through if its source
        register is 0 after decrementing. GDB must successfully break
        on the following instruction after stepping over.
     2. Check that dbnz branches to the target correctly if its source register
        is not 0 after decrementing - GDB must successfully break on the target
        instruction if a forward branch is performed after stepping over.
     3. The same as point 2 but for a backward branching case.

Signed-off-by: Yuriy Kolerov <kolerov93@gmail.com>
---
 gdb/arc-tdep.c                      | 10 +++
 gdb/testsuite/gdb.arch/arc-dbnz.S   | 47 ++++++++++++++
 gdb/testsuite/gdb.arch/arc-dbnz.exp | 97 +++++++++++++++++++++++++++++
 3 files changed, 154 insertions(+)
 create mode 100644 gdb/testsuite/gdb.arch/arc-dbnz.S
 create mode 100644 gdb/testsuite/gdb.arch/arc-dbnz.exp
  

Patch

diff --git a/gdb/arc-tdep.c b/gdb/arc-tdep.c
index 12d8aee949f..7dd43cc239f 100644
--- a/gdb/arc-tdep.c
+++ b/gdb/arc-tdep.c
@@ -466,6 +466,16 @@  arc_insn_get_branch_target (const struct arc_instruction &insn)
 	 instruction, hence last two bits should be truncated.  */
       return pcrel_addr + align_down (insn.address, 4);
     }
+  /* DBNZ is the only branch instruction that keeps a branch address in
+     the second operand. It must be intercepted and treated differently. */
+  else if (insn.insn_class == DBNZ)
+    {
+      CORE_ADDR pcrel_addr = arc_insn_get_operand_value_signed (insn, 1);
+
+      /* Offset is relative to the 4-byte aligned address of the current
+	 instruction, hence last two bits should be truncated. */
+      return pcrel_addr + align_down (insn.address, 4);
+    }
   /* B, Bcc, BL, BLcc, LP, LPcc: PC = currentPC + operand.  */
   else if (insn.insn_class == BRANCH || insn.insn_class == LOOP)
     {
diff --git a/gdb/testsuite/gdb.arch/arc-dbnz.S b/gdb/testsuite/gdb.arch/arc-dbnz.S
new file mode 100644
index 00000000000..45e1dfe8305
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/arc-dbnz.S
@@ -0,0 +1,47 @@ 
+; This testcase 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/>.
+
+	.section .text
+	.align 4
+	.global main
+	.type main, @function
+
+main:
+	mov r0,1
+dbnz1:
+	; r0 == 0 after decrementing. dbnz doesn't do branch.
+	dbnz r0,@end
+
+	mov r0,5
+dbnz2:
+	; r0 == 3 after decrementing and delay slot. dbnz does branch.
+	dbnz.d r0,@dbnz3
+	sub r0,r0,1
+
+dbnz4:
+	; r0 == 1 after decrementing. dbnz does branch.
+	dbnz r0,@end
+
+dbnz3:
+	; r0 == 2 after decrementing. dbnz does branch.
+	dbnz r0,@dbnz4
+
+end:
+	mov r0,0
+	j [blink]
+
+	.section .note.GNU-stack,"",@progbits
diff --git a/gdb/testsuite/gdb.arch/arc-dbnz.exp b/gdb/testsuite/gdb.arch/arc-dbnz.exp
new file mode 100644
index 00000000000..f1fce0e5a7f
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/arc-dbnz.exp
@@ -0,0 +1,97 @@ 
+# This testcase 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/>.
+
+# Test dbnz instruction. It decrements its source register operand, and if
+# the result is non-zero it branches to the location defined by a signed
+# half-word displacement operand.
+#
+# It's necessary to verify these cases:
+#
+#     1. Check that dbnz does not branch and falls through if its source
+#        register is 0 after decrementing. GDB must successfully break
+#        on the following instruction after stepping over.
+#     2. Check that dbnz branches to the target correctly if its source register
+#        is not 0 after decrementing - GDB must successfully break on the target
+#        instruction if a forward branch is performed after stepping over.
+#     3. The same as point 2 but for a backward branching case.
+
+require {istarget "arc*-*-*"}
+
+standard_testfile .S
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+if ![runto_main] {
+    return 0
+}
+
+gdb_test "break dbnz1" \
+	 "Breakpoint $decimal at .*" \
+	 "set breakpoint on the 1st dbnz"
+
+gdb_test "break dbnz2" \
+	 "Breakpoint $decimal at .*" \
+	 "set breakpoint on the 2nd dbnz"
+
+gdb_test "break dbnz3" \
+	 "Breakpoint $decimal at .*" \
+	 "set breakpoint on the 3rd dbnz"
+
+gdb_test "break dbnz4" \
+	 "Breakpoint $decimal at .*" \
+	 "set breakpoint on the 4th dbnz"
+
+gdb_test "break end" \
+	 "Breakpoint $decimal at .*" \
+	 "set breakpoint at the end"
+
+gdb_test "continue" \
+	 "Breakpoint $decimal, dbnz1.*dbnz r0,@end" \
+	 "continue to the 1st dbnz"
+
+gdb_test "x /i \$pc" \
+	 "$hex <.*>:\[ \t\]+dbnz\[ \t\]+r0,24.*" \
+	 "stayng on the 1st dbnz instruction"
+
+gdb_test "stepi" \
+	 "mov r0,5" \
+	 "step over the 1st dbnz, branch is not taken"
+
+gdb_test "stepi" \
+	 "Breakpoint $decimal, dbnz2.*dbnz\\.d r0,@dbnz3" \
+	 "step over r0 initialization, staying on the 2nd dbnz"
+
+# Linux steps over delay slot after "stepi", but stubs with hardware stepping
+# like nSIM's stub may step right on delay slot. Thus use "continue" instead of
+# "stepi" to make this test work for all platforms.
+gdb_test "continue" \
+	 "Breakpoint $decimal, dbnz3.*dbnz r0,@dbnz4" \
+	 "step over the 2nd dbnz, branch is taken, staying on the 3rd dbnz"
+
+gdb_test "stepi" \
+	 "Breakpoint $decimal, dbnz4.*dbnz r0,@end" \
+	 "step over the 3rd dbnz, branch is taken, staying on the 4th dbnz"
+
+gdb_test "stepi" \
+	 "Breakpoint $decimal, end.*mov r0,0" \
+	 "step over the 4th dbnz, branch is taken, staying on the epilogue"
+
+gdb_test "info register r0" \
+	 "r0\[ \t\]+0x1\[ \t\]+1" \
+	 "r0 contains 1 after all dbnz instructions"