[PATCH, RFC] AArch64: Implement software single step

Collin May collin@collinswebsite.com
Sun Jan 27 22:52:00 GMT 2019


This moves the functionality that was previously in the
aarch64_software_single_step function to a new aarch64_deal_with_atomic_sequence
function, and if an atomic sequence is not detected, it will attempt to predict
the next location of the program counter by detecting branch instructions and
predicting their outcomes, much like how arm_get_next_pcs works.

Although AArch64 platforms typically support hardware single step, some kernels
do not. This functionality is useful when interacting with remote targets written
to run under such kernels (and to avoid sending them 's' operations in vCont when
they do not advertise support for the 's' operation).

I've noticed that the arm_software_single_step functionality is largely delegated
to an "arm_get_next_pcs" system that seems to be shared with gdbserver. Since, as
far as I can tell, gdbserver on AArch64 is only intended to run under kernels
that support hardware single step, I don't think there's any need for this code
to be shared with gdbserver.

Finally, one might notice that I haven't written tests for this functionality.
I'm not familiar with gdb's testsuite and would appreciate feedback on how to go
about writing tests for this. I have manually tested that this is working
correctly on a platform that does not support hardware single step.
---
 gdb/aarch64-tdep.c | 133 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 130 insertions(+), 3 deletions(-)

diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index bc928e14e9..1b50ec5fba 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -2489,11 +2489,12 @@ value_of_aarch64_user_reg (struct frame_info *frame, const void *baton)
 }
 
 
-/* Implement the "software_single_step" gdbarch method, needed to
-   single step through atomic sequences on AArch64.  */
+/* Checks for an atomic sequence of instructions.  If such a sequence
+   is found, attempt to step through it.  The end of the sequence address is
+   added to the next_pcs list.  */
 
 static std::vector<CORE_ADDR>
-aarch64_software_single_step (struct regcache *regcache)
+aarch64_deal_with_atomic_sequence (struct regcache *regcache)
 {
   struct gdbarch *gdbarch = regcache->arch ();
   enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
@@ -2573,6 +2574,132 @@ aarch64_software_single_step (struct regcache *regcache)
   return next_pcs;
 }
 
+/* Returns true if the condition evaluates to true. */
+
+static int
+condition_true (unsigned long cond, unsigned long cpsr)
+{
+  int result = 0;
+
+  int pstate_n = bit (cpsr, 31);
+  int pstate_z = bit (cpsr, 30);
+  int pstate_c = bit (cpsr, 29);
+  int pstate_v = bit (cpsr, 28);
+
+  switch ((cond >> 1) & 3) {
+  case 0:
+    result = pstate_z;
+    break;
+  case 1:
+    result = pstate_c;
+    break;
+  case 2:
+    result = pstate_n;
+    break;
+  case 3:
+    result = pstate_v;
+    break;
+  case 4:
+    result = (pstate_c == 1) && (pstate_z == 0);
+    break;
+  case 5:
+    result = (pstate_n == pstate_v);
+    break;
+  case 6:
+    result = (pstate_n == pstate_v) && (pstate_z == 0);
+    break;
+  case 7:
+    result = 1;
+    break;
+  }
+
+  if ((cond & 1) == 1 && cond != 0xf) {
+    result = !result;
+  }
+
+  return result;
+}
+
+/* Implement the "software_single_step" gdbarch method.  */
+
+static std::vector<CORE_ADDR>
+aarch64_software_single_step (struct regcache *regcache)
+{
+  struct gdbarch *gdbarch = regcache->arch ();
+  enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
+  const int insn_size = 4;
+
+  CORE_ADDR pc = regcache_read_pc (regcache);
+  unsigned long status = regcache_raw_get_unsigned (regcache, AARCH64_CPSR_REGNUM);
+  unsigned long pc_val = (unsigned long) pc;
+  CORE_ADDR branch_addr = (CORE_ADDR) (pc_val + insn_size);	/* Default case */
+
+  uint32_t insn = read_memory_unsigned_integer (pc, insn_size,
+						byte_order_for_code);
+
+  std::vector<CORE_ADDR> next_pcs;
+
+  next_pcs = aarch64_deal_with_atomic_sequence (regcache);
+  if (next_pcs.empty ()) {
+    aarch64_inst inst;
+    if (aarch64_decode_insn (insn, &inst, 1, NULL) != 0)
+      return {};
+
+    /* According to ISA_v82A_A64_xml_00bet3.1, in
+       AArch64 mode, the only things that can touch the
+       PC register are:
+       - the instructions decoded below
+       - AArch64.TakeReset
+       - AArch64.TakeException
+       - AArch64.ExceptionReturn (eret)
+       - ExitDebugState (drps) */
+
+    if (inst.opcode->iclass == condbranch)
+      {
+	/* b.cond */
+	if (condition_true (inst.cond->value, status))
+	  branch_addr = pc + inst.operands[0].imm.value;
+      }
+    else if (inst.opcode->iclass == branch_imm)
+      {
+	/* b, bl */
+	branch_addr = pc + inst.operands[0].imm.value;
+      }
+    else if (inst.opcode->iclass == branch_reg)
+      {
+	/* br, blr, ret */
+	branch_addr = regcache_raw_get_unsigned (regcache,
+						 inst.operands[0].reg.regno);
+      }
+    else if (inst.opcode->iclass == compbranch)
+      {
+	/* cbz, cbnz */
+	ULONGEST reg = regcache_raw_get_unsigned (regcache,
+						  inst.operands[0].reg.regno);
+	int op = bit (insn, 24); // cbz vs cbnz
+
+	if (inst.operands[0].qualifier == AARCH64_OPND_QLF_W) // sf
+	  reg &= 0xffffffff;
+
+	if ((reg == 0) == (op == 0))
+	  branch_addr = pc + inst.operands[1].imm.value;
+      }
+    else if (inst.opcode->iclass == testbranch)
+      {
+	/* tbz, tbnz */
+	ULONGEST reg = regcache_raw_get_unsigned (regcache,
+						  inst.operands[0].reg.regno);
+	int bit_val = bit (insn, 24); // tbz vs tbnz
+	if (bit (reg, inst.operands[1].imm.value) == bit_val)
+	  branch_addr = pc + inst.operands[2].imm.value;
+      }
+
+    next_pcs.push_back (branch_addr);
+  }
+
+  return next_pcs;
+}
+
 struct aarch64_displaced_step_closure : public displaced_step_closure
 {
   /* It is true when condition instruction, such as B.CON, TBZ, etc,
-- 
2.20.1



More information about the Gdb-patches mailing list