This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] Displaced stepping (non-stop debugging) support for ARM Linux


Hi,

These patches provide an implementation of displaced stepping support
for ARM (Linux only for now), using the generic hooks provided by GDB.

ARM support is relatively tricky compared to some other architectures,
because there's no hardware single-stepping support. However we can
fake it by making sure that displaced instructions don't modify control
flow, and placing a software breakpoint after each displaced
instruction. Also registers are rewritten to handle instructions which
might read/write the PC. We must of course take care that the cleanup
routine puts things back in the correct places.

As a side-effect of the lack of h/w single-stepping support, we've
enabled displaced stepping in all cases, not just when stepping over
breakpoints (a patch of Pedro Alves's, attached, but mangled by me to
apply to mainline). I'm not sure if that's the most sensible approach
(for displaced stepping, we only care about not *removing* breakpoints
which might be hit by other threads. We can still add temporary
breakpoints for the purpose of software single-stepping).

Only the traditional ARM instruction set is covered by this patch --
there's no support for Thumb or Thumb-2. For ARM instructions, the
coverage is pretty good though I think.

Note that though this implementation is loosely inspired by the Linux
kernel's kprobes implementation, no code has been taken from there.

Regression tested using an x86 host and a remote target running
gdbserver, with (possibly) no regressions -- although several tests
seem to fluctuate randomly between passing/failing for me (with
timeouts) with or without the patch. I'm not sure how normal that is.
Also tested with "GDBFLAGS=-ex 'set displaced-stepping on'", which
seemed OK, and of course with hand-written spot-checks.

OK to apply, or any comments?

Cheers,

Julian

ChangeLog (always use displaced stepping)

2008-11-19  Pedro Alves  <pedro@codesourcery.com>

    * infrun.c (displaced_step_fixup): If this is a software
    single-stepping arch, don't tell the target to single-step.
    (resume): If this is a software single-stepping arch, and
    displaced-stepping is enabled, use it for all single-step
    requests.  

ChangeLog (ARM displaced stepping)

    gdb/
    * arm-linux-tdep.c (arch-utils.h): Include file.
    (arm_linux_init_abi): Initialise displaced stepping callbacks.
    * arm-tdep.c (DISPLACED_TEMPS, DISPLACED_MODIFIED_INSNS): New
    macros.
    (struct displaced_step_closure): Define.
    (displaced_read_reg, displaced_write_reg, copy_unmodified)
    (copy_preload, copy_preload_reg, copy_copro_load_store)
    (copy_b_bl_blx, copy_bx_blx_reg, copy_dp_imm, copy_dp_reg)
    (copy_dp_shifted_reg, modify_store_pc, copy_extra_ld_st)
    (copy_ldr_str_ldrb_strb, copy_block_xfer, copy_svc, copy_undef)
    (copy_unpred): New.
    (cleanup_branch, cleanup_dp_imm, cleanup_dp_reg)
    (cleanup_dp_shifted_reg, cleanup_load, cleanup_store)
    (cleanup_block_xfer, cleanup_svc, cleanup_kernel_helper_return)
    (cleanup_preload, cleanup_copro_load_store): New functions (with
    forward declarations).
    (decode_misc_memhint_neon, decode_unconditional)
    (decode_miscellaneous, decode_dp_misc, decode_ld_st_word_ubyte)
    (decode_media, decode_b_bl_ldmstm, decode_ext_reg_ld_st)
    (decode_svc_copro, arm_process_displaced_insn)
    (arm_catch_kernel_helper_return, arm_displaced_step_copy_insn)
    (arm_displaced_step_fixup): New.
    (arm_gdbarch_init): Initialise max insn length field.
    * arm-tdep.h (arm_displaced_step_copy_insn)
    (arm_displaced_step_fixup): Add prototypes.
--- .pc/arm-displaced-stepping/gdb/arm-linux-tdep.c	2009-01-20 13:22:42.000000000 -0800
+++ gdb/arm-linux-tdep.c	2009-01-20 13:23:59.000000000 -0800
@@ -37,6 +37,7 @@
 #include "arm-tdep.h"
 #include "arm-linux-tdep.h"
 #include "glibc-tdep.h"
+#include "arch-utils.h"
 
 #include "gdb_string.h"
 
@@ -647,6 +648,14 @@ arm_linux_init_abi (struct gdbarch_info 
   /* Core file support.  */
   set_gdbarch_regset_from_core_section (gdbarch,
 					arm_linux_regset_from_core_section);
+
+  /* Displaced stepping.  */
+  set_gdbarch_displaced_step_copy_insn (gdbarch,
+					arm_displaced_step_copy_insn);
+  set_gdbarch_displaced_step_fixup (gdbarch, arm_displaced_step_fixup);
+  set_gdbarch_displaced_step_free_closure (gdbarch,
+					   simple_displaced_step_free_closure);
+  set_gdbarch_displaced_step_location (gdbarch, displaced_step_at_entry_point);
 }
 
 void
--- .pc/arm-displaced-stepping/gdb/arm-tdep.c	2009-01-20 13:22:42.000000000 -0800
+++ gdb/arm-tdep.c	2009-01-20 13:33:10.000000000 -0800
@@ -2175,6 +2175,1456 @@ arm_software_single_step (struct frame_i
   return 1;
 }
 
+/* Displaced stepping support.  */
+
+/* The maximum number of temporaries available for displaced instructions.  */
+#define DISPLACED_TEMPS			5
+/* The maximum number of modified instructions generated for one single-stepped
+   instruction.  */
+#define DISPLACED_MODIFIED_INSNS	5
+
+struct displaced_step_closure
+{
+  ULONGEST tmp[DISPLACED_TEMPS];
+  int rd;
+  int wrote_to_pc;
+  union
+  {
+    struct
+    {
+      int xfersize;
+      int rn;			   /* Writeback register.  */
+      unsigned int immed : 1;      /* Offset is immediate.  */
+      unsigned int writeback : 1;  /* Perform base-register writeback.  */
+    } ldst;
+
+    struct
+    {
+      unsigned long dest;
+      unsigned int link : 1;
+      unsigned int exchange : 1;
+    } branch;
+
+    struct
+    {
+      unsigned int regmask;
+      int rn;
+      CORE_ADDR xfer_addr;
+      unsigned int load : 1;
+      unsigned int user : 1;
+      unsigned int increment : 1;
+      unsigned int before : 1;
+      unsigned int writeback : 1;
+    } block;
+
+    struct
+    {
+      unsigned int immed : 1;
+    } preload;
+  } u;
+  unsigned long modinsn[DISPLACED_MODIFIED_INSNS];
+  int numinsns;
+  CORE_ADDR insn_addr;
+  void (*cleanup) (struct regcache *, struct displaced_step_closure *);
+};
+
+static void cleanup_branch (struct regcache *, struct displaced_step_closure *);
+static void cleanup_dp_imm (struct regcache *, struct displaced_step_closure *);
+static void cleanup_dp_reg (struct regcache *, struct displaced_step_closure *);
+static void cleanup_dp_shifted_reg (struct regcache *,
+				    struct displaced_step_closure *);
+static void cleanup_load (struct regcache *, struct displaced_step_closure *);
+static void cleanup_store (struct regcache *, struct displaced_step_closure *);
+static void cleanup_block_xfer (struct regcache *,
+				struct displaced_step_closure *);
+static void cleanup_svc (struct regcache *, struct displaced_step_closure *);
+static void cleanup_kernel_helper_return (struct regcache *,
+					  struct displaced_step_closure *);
+static void cleanup_preload (struct regcache *,
+			     struct displaced_step_closure *);
+static void cleanup_copro_load_store (struct regcache *,
+				      struct displaced_step_closure *);
+
+ULONGEST
+displaced_read_reg (struct regcache *regs, CORE_ADDR from, int regno)
+{
+  ULONGEST ret;
+
+  if (regno == 15)
+    {
+      if (debug_displaced)
+        fprintf_unfiltered (gdb_stdlog, "displaced: read pc value %.8lx\n",
+			    (unsigned long) from + 8);
+      return (ULONGEST) from + 8;  /* Pipeline offset.  */
+    }
+  else
+    {
+      regcache_cooked_read_unsigned (regs, regno, &ret);
+      if (debug_displaced)
+        fprintf_unfiltered (gdb_stdlog, "displaced: read r%d value %.8lx\n",
+			    regno, (unsigned long) ret);
+      return ret;
+    }
+}
+
+static void arm_write_pc (struct regcache *, CORE_ADDR);
+
+void
+displaced_write_reg (struct regcache *regs, struct displaced_step_closure *dsc,
+		     int regno, ULONGEST val)
+{
+  if (regno == 15)
+    {
+      if (debug_displaced)
+        fprintf_unfiltered (gdb_stdlog, "displaced: writing pc %.8lx\n",
+			    (unsigned long) val);
+      arm_write_pc (regs, val);
+      dsc->wrote_to_pc = 1;
+    }
+  else
+    {
+      if (debug_displaced)
+        fprintf_unfiltered (gdb_stdlog, "displaced: writing r%d value %.8lx\n",
+			    regno, (unsigned long) val);
+      regcache_cooked_write_unsigned (regs, regno, val);
+    }
+}
+
+static int
+copy_unmodified (unsigned long insn, const char *iname,
+		 struct displaced_step_closure *dsc)
+{
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying insn %.8lx, "
+			"opcode/class '%s'\n", insn, iname);
+
+  dsc->modinsn[0] = insn;
+
+  return 0;
+}
+
+static int
+copy_preload (unsigned long insn, struct regcache *regs,
+	      struct displaced_step_closure *dsc)
+{
+  unsigned int rn = bits (insn, 16, 19);
+  ULONGEST rn_val;
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying preload insn %.8lx\n",
+			insn);
+
+  /* Preload instructions:
+
+     {pli/pld} [rn, #+/-imm]
+     ->
+     {pli/pld} [r0, #+/-imm].  */
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  rn_val = displaced_read_reg (regs, from, rn);
+  displaced_write_reg (regs, dsc, 0, rn_val);
+
+  dsc->u.preload.immed = 1;
+
+  dsc->modinsn[0] = insn & 0xfff0ffff;
+
+  dsc->cleanup = &cleanup_preload;
+
+  return 0;
+}
+
+static int
+copy_preload_reg (unsigned long insn, struct regcache *regs,
+		  struct displaced_step_closure *dsc)
+{
+  unsigned int rn = bits (insn, 16, 19);
+  unsigned int rm = bits (insn, 0, 3);
+  ULONGEST rn_val, rm_val;
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying preload insn %.8lx\n",
+			insn);
+
+  /* Preload register-offset instructions:
+
+     {pli/pld} [rn, rm {, shift}]
+     ->
+     {pli/pld} [r0, r1 {, shift}].  */
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  dsc->tmp[1] = displaced_read_reg (regs, from, 1);
+  rn_val = displaced_read_reg (regs, from, rn);
+  rm_val = displaced_read_reg (regs, from, rm);
+  displaced_write_reg (regs, dsc, 0, rn_val);
+  displaced_write_reg (regs, dsc, 1, rm_val);
+
+  dsc->u.preload.immed = 0;
+
+  dsc->modinsn[0] = (insn & 0xfff0fff0) | 0x1;
+
+  dsc->cleanup = &cleanup_preload;
+
+  return 0;
+}
+
+static void
+cleanup_preload (struct regcache *regs, struct displaced_step_closure *dsc)
+{
+  displaced_write_reg (regs, dsc, 0, dsc->tmp[0]);
+  if (!dsc->u.preload.immed)
+    displaced_write_reg (regs, dsc, 1, dsc->tmp[1]);
+}
+
+static int
+copy_copro_load_store (unsigned long insn, struct regcache *regs,
+		       struct displaced_step_closure *dsc)
+{
+  unsigned int rn = bits (insn, 16, 19);
+  ULONGEST rn_val;
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying coprocessor "
+			"load/store insn %.8lx\n", insn);
+
+  /* Coprocessor load/store instructions:
+
+     {stc/stc2} [<Rn>, #+/-imm]  (and other immediate addressing modes)
+     ->
+     {stc/stc2} [r0, #+/-imm].
+
+     ldc/ldc2 are handled identically.  */
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  rn_val = displaced_read_reg (regs, from, rn);
+  displaced_write_reg (regs, dsc, 0, rn_val);
+
+  dsc->u.ldst.writeback = bit (insn, 25);
+  dsc->u.ldst.rn = rn;
+
+  dsc->modinsn[0] = insn & 0xfff0ffff;
+
+  dsc->cleanup = &cleanup_copro_load_store;
+
+  return 0;
+}
+
+static void
+cleanup_copro_load_store (struct regcache *regs,
+			  struct displaced_step_closure *dsc)
+{
+  ULONGEST rn_val = displaced_read_reg (regs, dsc->insn_addr, 0);
+
+  displaced_write_reg (regs, dsc, 0, dsc->tmp[0]);
+
+  if (dsc->u.ldst.writeback)
+    displaced_write_reg (regs, dsc, dsc->u.ldst.rn, rn_val);
+}
+
+static int
+copy_b_bl_blx (unsigned long insn, struct regcache *regs,
+	       struct displaced_step_closure *dsc)
+{
+  unsigned int cond = bits (insn, 28, 31);
+  int exchange = (cond == 0xf);
+  int link = exchange || bit (insn, 24);
+  CORE_ADDR from = dsc->insn_addr;
+  long offset;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying %s immediate insn "
+			"%.8lx\n", (exchange) ? "blx" : (link) ? "bl" : "b",
+			insn);
+
+  offset = bits (insn, 0, 23) << 2;
+  if (bit (offset, 25))
+    offset = offset | ~0x3ffffff;
+
+  /* Implement "BL<cond> <label>" as:
+
+     Preparation: tmp <- r0; r0 <- #0
+     Insn: mov<cond> r0, #1
+     Cleanup: if (r0) { r14 <- pc; pc <- label }, r0 <- tmp.
+
+     B<cond> similar, but don't set r14 in cleanup.  */
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  displaced_write_reg (regs, dsc, 0, 0);
+
+  dsc->u.branch.link = link;
+  dsc->u.branch.exchange = exchange;
+  dsc->u.branch.dest = from + 8 + offset;
+
+  if (exchange)
+    /* Implement as actual blx?  */
+    dsc->modinsn[0] = 0xe3a00001;  /* mov r0, #1.  */
+  else
+    dsc->modinsn[0] = (cond << 28) | 0x3a00001;  /* mov<cond> r0, #1.  */
+
+  dsc->cleanup = &cleanup_branch;
+
+  return 0;
+}
+
+static int
+copy_bx_blx_reg (unsigned long insn, struct regcache *regs,
+		 struct displaced_step_closure *dsc)
+{
+  unsigned int cond = bits (insn, 28, 31);
+  /* BX:  x12xxx1x
+     BLX: x12xxx3x.  */
+  int link = bit (insn, 5);
+  unsigned int rm = bits (insn, 0, 3);
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying %s register insn "
+			"%.8lx\n", (link) ? "blx" : "bx", insn);
+
+  /* Implement {BX,BLX}<cond> <reg>" as:
+
+     Preparation: dest <- rm; tmp <- r0; r0 <- #0
+     Insn: mov<cond> r0, #1
+     Cleanup: if (r0) { r14 <- pc; pc <- dest; }, r0 <- tmp.
+
+     Don't set r14 in cleanup for BX.  */
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  dsc->u.branch.dest = displaced_read_reg (regs, from, rm);
+  displaced_write_reg (regs, dsc, 0, 0);
+
+  dsc->u.branch.link = link;
+  dsc->u.branch.exchange = 1;
+
+  dsc->modinsn[0] = (cond << 28) | 0x3a00001;  /* mov<cond> r0, #1.  */
+
+  dsc->cleanup = &cleanup_branch;
+
+  return 0;
+}
+
+static void
+cleanup_branch (struct regcache *regs, struct displaced_step_closure *dsc)
+{
+  ULONGEST from = dsc->insn_addr;
+  ULONGEST branch_taken = displaced_read_reg (regs, from, 0);
+
+  displaced_write_reg (regs, dsc, 0, dsc->tmp[0]);
+
+  if (!branch_taken)
+    return;
+
+  if (dsc->u.branch.link)
+    {
+      ULONGEST pc = displaced_read_reg (regs, from, 15);
+      displaced_write_reg (regs, dsc, 14, pc - 4);
+    }
+
+  /* FIXME: BLX immediate is probably broken!  */
+
+  displaced_write_reg (regs, dsc, 15, dsc->u.branch.dest);
+}
+
+static int
+copy_dp_imm (unsigned long insn, struct regcache *regs,
+	     struct displaced_step_closure *dsc)
+{
+  unsigned int rn = bits (insn, 16, 19);
+  unsigned int rd = bits (insn, 12, 15);
+  unsigned int op = bits (insn, 21, 24);
+  int is_mov = (op == 0xd);
+  ULONGEST rd_val, rn_val;
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying immediate %s insn "
+			"%.8lx\n", is_mov ? "move" : "data-processing", insn);
+
+  /* Instruction is of form:
+
+     <op><cond> rd, [rn,] #imm
+
+     Rewrite as:
+
+     Preparation: tmp1, tmp2 <- r0, r1;
+		  r0, r1 <- rd, rn
+     Insn: <op><cond> r0, r1, #imm
+     Cleanup: rd <- r0; r0 <- tmp1; r1 <- tmp2
+  */
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  dsc->tmp[1] = displaced_read_reg (regs, from, 1);
+  rn_val = displaced_read_reg (regs, from, rn);
+  rd_val = displaced_read_reg (regs, from, rd);
+  displaced_write_reg (regs, dsc, 0, rd_val);
+  displaced_write_reg (regs, dsc, 1, rn_val);
+  dsc->rd = rd;
+
+  if (is_mov)
+    dsc->modinsn[0] = insn & 0xfff00fff;
+  else
+    dsc->modinsn[0] = (insn & 0xfff00fff) | 0x10000;
+
+  dsc->cleanup = &cleanup_dp_imm;
+
+  return 0;
+}
+
+static void
+cleanup_dp_imm (struct regcache *regs, struct displaced_step_closure *dsc)
+{
+  ULONGEST rd_val = displaced_read_reg (regs, dsc->insn_addr, 0);
+  displaced_write_reg (regs, dsc, 0, dsc->tmp[0]);
+  displaced_write_reg (regs, dsc, 1, dsc->tmp[1]);
+  displaced_write_reg (regs, dsc, dsc->rd, rd_val);
+}
+
+static int
+copy_dp_reg (unsigned long insn, struct regcache *regs,
+	     struct displaced_step_closure *dsc)
+{
+  unsigned int rn = bits (insn, 16, 19);
+  unsigned int rm = bits (insn, 0, 3);
+  unsigned int rd = bits (insn, 12, 15);
+  unsigned int op = bits (insn, 21, 24);
+  int is_mov = (op == 0xd);
+  ULONGEST rd_val, rn_val, rm_val;
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying reg %s insn %.8lx\n",
+			is_mov ? "move" : "data-processing", insn);
+
+  /* Instruction is of form:
+
+     <op><cond> rd, [rn,] rm [, <shift>]
+
+     Rewrite as:
+
+     Preparation: tmp1, tmp2, tmp3 <- r0, r1, r2;
+		  r0, r1, r2 <- rd, rn, rm
+     Insn: <op><cond> r0, r1, r2 [, <shift>]
+     Cleanup: rd <- r0; r0, r1, r2 <- tmp1, tmp2, tmp3
+  */
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  dsc->tmp[1] = displaced_read_reg (regs, from, 1);
+  dsc->tmp[2] = displaced_read_reg (regs, from, 2);
+  rd_val = displaced_read_reg (regs, from, rd);
+  rn_val = displaced_read_reg (regs, from, rn);
+  rm_val = displaced_read_reg (regs, from, rm);
+  displaced_write_reg (regs, dsc, 0, rd_val);
+  displaced_write_reg (regs, dsc, 1, rn_val);
+  displaced_write_reg (regs, dsc, 2, rm_val);
+  dsc->rd = rd;
+
+  if (is_mov)
+    dsc->modinsn[0] = (insn & 0xfff00ff0) | 0x2;
+  else
+    dsc->modinsn[0] = (insn & 0xfff00ff0) | 0x10002;
+
+  dsc->cleanup = &cleanup_dp_reg;
+
+  return 0;
+}
+
+static void
+cleanup_dp_reg (struct regcache *regs, struct displaced_step_closure *dsc)
+{
+  ULONGEST rd_val;
+  int i;
+
+  rd_val = displaced_read_reg (regs, dsc->insn_addr, 0);
+
+  for (i = 0; i < 3; i++)
+    displaced_write_reg (regs, dsc, i, dsc->tmp[i]);
+
+  displaced_write_reg (regs, dsc, dsc->rd, rd_val);
+}
+
+static int
+copy_dp_shifted_reg (unsigned long insn, struct regcache *regs,
+		     struct displaced_step_closure *dsc)
+{
+  unsigned int rn = bits (insn, 16, 19);
+  unsigned int rm = bits (insn, 0, 3);
+  unsigned int rd = bits (insn, 12, 15);
+  unsigned int rs = bits (insn, 8, 11);
+  unsigned int op = bits (insn, 21, 24);
+  int is_mov = (op == 0xd), i;
+  ULONGEST rd_val, rn_val, rm_val, rs_val;
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying shifted reg %s insn "
+			"%.8lx\n", is_mov ? "move" : "data-processing", insn);
+
+  /* Instruction is of form:
+
+     <op><cond> rd, [rn,] rm, <shift> rs
+
+     Rewrite as:
+
+     Preparation: tmp1, tmp2, tmp3, tmp4 <- r0, r1, r2, r3
+		  r0, r1, r2, r3 <- rd, rn, rm, rs
+     Insn: <op><cond> r0, r1, r2, <shift> r3
+     Cleanup: tmp5 <- r0
+	      r0, r1, r2, r3 <- tmp1, tmp2, tmp3, tmp4
+	      rd <- tmp5
+  */
+
+  for (i = 0; i < 4; i++)
+    dsc->tmp[i] = displaced_read_reg (regs, from, i);
+
+  rd_val = displaced_read_reg (regs, from, rd);
+  rn_val = displaced_read_reg (regs, from, rn);
+  rm_val = displaced_read_reg (regs, from, rm);
+  rs_val = displaced_read_reg (regs, from, rs);
+  displaced_write_reg (regs, dsc, 0, rd_val);
+  displaced_write_reg (regs, dsc, 1, rn_val);
+  displaced_write_reg (regs, dsc, 2, rm_val);
+  displaced_write_reg (regs, dsc, 3, rs_val);
+  dsc->rd = rd;
+
+  if (is_mov)
+    dsc->modinsn[0] = (insn & 0xfff000f0) | 0x302;
+  else
+    dsc->modinsn[0] = (insn & 0xfff000f0) | 0x10302;
+
+  dsc->cleanup = &cleanup_dp_shifted_reg;
+
+  return 0;
+}
+
+static void
+cleanup_dp_shifted_reg (struct regcache *regs,
+			struct displaced_step_closure *dsc)
+{
+  ULONGEST rd_val = displaced_read_reg (regs, dsc->insn_addr, 0);
+  int i;
+
+  for (i = 0; i < 4; i++)
+    displaced_write_reg (regs, dsc, i, dsc->tmp[i]);
+
+  displaced_write_reg (regs, dsc, dsc->rd, rd_val);
+}
+
+/* FIXME: This should depend on the arch version.  */
+
+static ULONGEST
+modify_store_pc (ULONGEST pc)
+{
+  return pc + 4;
+}
+
+static int
+copy_extra_ld_st (unsigned long insn, int unpriveleged, struct regcache *regs,
+		  struct displaced_step_closure *dsc)
+{
+  unsigned int op1 = bits (insn, 20, 24);
+  unsigned int op2 = bits (insn, 5, 6);
+  unsigned int rt = bits (insn, 12, 15);
+  unsigned int rn = bits (insn, 16, 19);
+  unsigned int rm = bits (insn, 0, 3);
+  char load[12]     = {0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1};
+  char bytesize[12] = {2, 2, 2, 2, 8, 1, 8, 1, 8, 2, 8, 2};
+  int immed = (op1 & 0x4) != 0;
+  int opcode;
+  ULONGEST rt_val, rt_val2 = 0, rn_val, rm_val = 0;
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying %sextra load/store "
+			"insn %.8lx\n", unpriveleged ? "unpriveleged " : "",
+			insn);
+
+  opcode = ((op2 << 2) | (op1 & 0x1) | ((op1 & 0x4) >> 1)) - 4;
+
+  if (opcode < 0)
+    internal_error (__FILE__, __LINE__,
+		    _("copy_extra_ld_st: instruction decode error"));
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  dsc->tmp[1] = displaced_read_reg (regs, from, 1);
+  dsc->tmp[2] = displaced_read_reg (regs, from, 2);
+  if (!immed)
+    dsc->tmp[3] = displaced_read_reg (regs, from, 3);
+
+  rt_val = displaced_read_reg (regs, from, rt);
+  if (bytesize[opcode] == 8)
+    rt_val2 = displaced_read_reg (regs, from, rt + 1);
+  rn_val = displaced_read_reg (regs, from, rn);
+  if (!immed)
+    rm_val = displaced_read_reg (regs, from, rm);
+
+  displaced_write_reg (regs, dsc, 0, rt_val);
+  if (bytesize[opcode] == 8)
+    displaced_write_reg (regs, dsc, 1, rt_val2);
+  displaced_write_reg (regs, dsc, 2, rn_val);
+  if (!immed)
+    displaced_write_reg (regs, dsc, 3, rm_val);
+
+  dsc->rd = rt;
+  dsc->u.ldst.xfersize = bytesize[opcode];
+  dsc->u.ldst.rn = rn;
+  dsc->u.ldst.immed = immed;
+  dsc->u.ldst.writeback = bit (insn, 24) == 0 || bit (insn, 21) != 0;
+
+  if (immed)
+    /* {ldr,str}<width><cond> rt, [rt2,] [rn, #imm]
+       ->
+       {ldr,str}<width><cond> r0, [r1,] [r2, #imm].  */
+    dsc->modinsn[0] = (insn & 0xfff00fff) | 0x20000;
+  else
+    /* {ldr,str}<width><cond> rt, [rt2,] [rn, +/-rm]
+       ->
+       {ldr,str}<width><cond> r0, [r1,] [r2, +/-r3].  */
+    dsc->modinsn[0] = (insn & 0xfff00ff0) | 0x20003;
+
+  dsc->cleanup = load[opcode] ? &cleanup_load : &cleanup_store;
+
+  return 0;
+}
+
+static int
+copy_ldr_str_ldrb_strb (unsigned long insn, struct regcache *regs,
+			struct displaced_step_closure *dsc, int load, int byte,
+			int usermode)
+{
+  int immed = !bit (insn, 25);
+  unsigned int rt = bits (insn, 12, 15);
+  unsigned int rn = bits (insn, 16, 19);
+  unsigned int rm = bits (insn, 0, 3);  /* Only valid if !immed.  */
+  ULONGEST rt_val, rn_val, rm_val = 0;
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying %s%s insn %.8lx\n",
+			load ? (byte ? "ldrb" : "ldr")
+			     : (byte ? "strb" : "str"), usermode ? "t" : "",
+			insn);
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  dsc->tmp[2] = displaced_read_reg (regs, from, 2);
+  if (!immed)
+    dsc->tmp[3] = displaced_read_reg (regs, from, 3);
+
+  rt_val = displaced_read_reg (regs, from, rt);
+  rn_val = displaced_read_reg (regs, from, rn);
+  if (!immed)
+    rm_val = displaced_read_reg (regs, from, rm);
+
+  if (!load && rt == 15)
+    rt_val = modify_store_pc (rt_val);
+
+  displaced_write_reg (regs, dsc, 0, rt_val);
+  displaced_write_reg (regs, dsc, 2, rn_val);
+  if (!immed)
+    displaced_write_reg (regs, dsc, 3, rm_val);
+
+  dsc->rd = rt;
+  dsc->u.ldst.xfersize = byte ? 1 : 4;
+  dsc->u.ldst.rn = rn;
+  dsc->u.ldst.immed = immed;
+  dsc->u.ldst.writeback = bit (insn, 24) == 0 || bit (insn, 21) != 0;
+
+  if (immed)
+    /* {ldr,str}[b]<cond> rt, [rn, #imm], etc.
+       ->
+       {ldr,str}[b]<cond> r0, [r2, #imm].  */
+    dsc->modinsn[0] = (insn & 0xfff00fff) | 0x20000;
+  else
+    /* {ldr,str}[b]<cond> rt, [rn, rm], etc.
+       ->
+       {ldr,str}[b]<cond> r0, [r2, r3].  */
+    dsc->modinsn[0] = (insn & 0xfff00ff0) | 0x20003;
+
+  dsc->cleanup = load ? &cleanup_load : &cleanup_store;
+
+  return 0;
+}
+
+static void
+cleanup_load (struct regcache *regs, struct displaced_step_closure *dsc)
+{
+  ULONGEST rt_val, rt_val2 = 0, rn_val;
+  CORE_ADDR from = dsc->insn_addr;
+
+  rt_val = displaced_read_reg (regs, from, 0);
+  if (dsc->u.ldst.xfersize == 8)
+    rt_val2 = displaced_read_reg (regs, from, 1);
+  rn_val = displaced_read_reg (regs, from, 2);
+
+  displaced_write_reg (regs, dsc, 0, dsc->tmp[0]);
+  if (dsc->u.ldst.xfersize > 4)
+    displaced_write_reg (regs, dsc, 1, dsc->tmp[1]);
+  displaced_write_reg (regs, dsc, 2, dsc->tmp[2]);
+  if (!dsc->u.ldst.immed)
+    displaced_write_reg (regs, dsc, 3, dsc->tmp[3]);
+
+  /* Handle register writeback.  */
+  if (dsc->u.ldst.writeback)
+    displaced_write_reg (regs, dsc, dsc->u.ldst.rn, rn_val);
+  /* Put result in right place.  */
+  displaced_write_reg (regs, dsc, dsc->rd, rt_val);
+  if (dsc->u.ldst.xfersize == 8)
+    displaced_write_reg (regs, dsc, dsc->rd + 1, rt_val2);
+}
+
+static void
+cleanup_store (struct regcache *regs, struct displaced_step_closure *dsc)
+{
+  CORE_ADDR from = dsc->insn_addr;
+  ULONGEST rn_val = displaced_read_reg (regs, from, 2);
+
+  displaced_write_reg (regs, dsc, 0, dsc->tmp[0]);
+  if (dsc->u.ldst.xfersize > 4)
+    displaced_write_reg (regs, dsc, 1, dsc->tmp[1]);
+  displaced_write_reg (regs, dsc, 2, dsc->tmp[2]);
+  if (!dsc->u.ldst.immed)
+    displaced_write_reg (regs, dsc, 3, dsc->tmp[3]);
+
+  /* Writeback.  */
+  if (dsc->u.ldst.writeback)
+    displaced_write_reg (regs, dsc, dsc->u.ldst.rn, rn_val);
+}
+
+/* Handle ldm/stm.  Doesn't handle any difficult cases (exception return,
+   user-register transfer).  */
+
+static int
+copy_block_xfer (unsigned long insn, struct regcache *regs,
+		 struct displaced_step_closure *dsc)
+{
+  int load = bit (insn, 20);
+  int user = bit (insn, 22);
+  int increment = bit (insn, 23);
+  int before = bit (insn, 24);
+  int writeback = bit (insn, 21);
+  int rn = bits (insn, 16, 19);
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying block transfer insn "
+			"%.8lx\n", insn);
+
+  /* ldm/stm is always emulated, because there are too many corner cases to
+     deal with otherwise.  Implement as mov<cond> r0, #1, then do actual
+     transfer in cleanup routine if condition passes.  FIXME: Non-priveleged
+     transfers.  */
+
+  /* Hmm, this might not work, because of memory permissions differing for
+     the debugger & the debugged program.  I wonder what to do about that?  */
+
+  dsc->u.block.xfer_addr = displaced_read_reg (regs, from, rn);
+  dsc->u.block.rn = rn;
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, 0);
+  displaced_write_reg (regs, dsc, 0, 0);
+
+  dsc->u.block.load = load;
+  dsc->u.block.user = user;
+  dsc->u.block.increment = increment;
+  dsc->u.block.before = before;
+  dsc->u.block.writeback = writeback;
+
+  dsc->u.block.regmask = insn & 0xffff;
+
+  dsc->modinsn[0] = (insn & 0xf0000000) | 0x3a00001;  /* mov<cond> r0, #1.  */
+
+  dsc->cleanup = &cleanup_block_xfer;
+
+  return 0;
+}
+
+static void
+cleanup_block_xfer (struct regcache *regs, struct displaced_step_closure *dsc)
+{
+  ULONGEST do_transfer;
+  ULONGEST from = dsc->insn_addr;
+  int inc = dsc->u.block.increment;
+  int bump_before = dsc->u.block.before ? (inc ? 4 : -4) : 0;
+  int bump_after = dsc->u.block.before ? 0 : (inc ? 4 : -4);
+  unsigned long regmask = dsc->u.block.regmask;
+  int regno = inc ? 0 : 15;
+  CORE_ADDR xfer_addr = dsc->u.block.xfer_addr;
+
+  do_transfer = displaced_read_reg (regs, from, 0);
+
+  displaced_write_reg (regs, dsc, 0, dsc->tmp[0]);
+
+  if (!do_transfer)
+    return;
+
+  /* FIXME: Implement non-priveleged transfers!  */
+  gdb_assert (!dsc->u.block.user);
+
+  /* FIXME: Exception return.  */
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: emulating block transfer: "
+			"%s %s %s\n", dsc->u.block.load ? "ldm" : "stm",
+			dsc->u.block.increment ? "inc" : "dec",
+			dsc->u.block.before ? "before" : "after");
+
+  while (regmask)
+    {
+      if (inc)
+	while (regno <= 15 && (regmask & (1 << regno)) == 0)
+	  regno++;
+      else
+        while (regno >= 0 && (regmask & (1 << regno)) == 0)
+	  regno--;
+
+      xfer_addr += bump_before;
+
+      if (dsc->u.block.load)
+        {
+	  unsigned long memword = read_memory_unsigned_integer (xfer_addr, 4);
+	  displaced_write_reg (regs, dsc, regno, memword);
+	}
+      else
+        {
+	  ULONGEST regval = displaced_read_reg (regs, from, regno);
+	  if (regno == 15)
+	    regval = modify_store_pc (regval);
+	  write_memory_unsigned_integer (xfer_addr, 4, regval);
+	}
+
+      xfer_addr += bump_after;
+
+      regmask &= ~(1 << regno);
+    }
+
+  if (dsc->u.block.writeback)
+    displaced_write_reg (regs, dsc, dsc->u.block.rn, xfer_addr);
+}
+
+static int
+copy_svc (unsigned long insn, CORE_ADDR to, struct regcache *regs,
+	  struct displaced_step_closure *dsc)
+{
+  CORE_ADDR from = dsc->insn_addr;
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying svc insn %.8lx\n",
+			insn);
+
+  /* Preparation: tmp[0] <- to.
+     Insn: unmodified svc.
+     Cleanup: if (pc == <scratch>+4) pc <- insn_addr + 4;
+	      else leave PC alone.  */
+
+  dsc->tmp[0] = to;
+  dsc->modinsn[0] = insn;
+
+  dsc->cleanup = &cleanup_svc;
+  /* Pretend we wrote to the PC, so cleanup doesn't set PC to the next
+     instruction.  */
+  dsc->wrote_to_pc = 1;
+
+  return 0;
+}
+
+static void
+cleanup_svc (struct regcache *regs, struct displaced_step_closure *dsc)
+{
+  CORE_ADDR from = dsc->insn_addr;
+  CORE_ADDR to = dsc->tmp[0];
+  ULONGEST pc;
+
+  /* Note: we want the real PC, so don't use displaced_read_reg here.  */
+  regcache_cooked_read_unsigned (regs, ARM_PC_REGNUM, &pc);
+
+  if (pc == to + 4)
+    displaced_write_reg (regs, dsc, ARM_PC_REGNUM, from + 4);
+
+  /* FIXME: What can we do about signal trampolines?  */
+}
+
+static int
+copy_undef (unsigned long insn, struct displaced_step_closure *dsc)
+{
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying undefined insn %.8lx\n",
+			insn);
+
+  dsc->modinsn[0] = insn;
+
+  return 0;
+}
+
+static int
+copy_unpred (unsigned long insn, struct displaced_step_closure *dsc)
+{
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copying unpredictable insn "
+			"%.8lx\n", insn);
+
+  dsc->modinsn[0] = insn;
+
+  return 0;
+}
+
+static int
+decode_misc_memhint_neon (unsigned long insn, struct regcache *regs,
+			  struct displaced_step_closure *dsc)
+{
+  unsigned int op1 = bits (insn, 20, 26), op2 = bits (insn, 4, 7);
+  unsigned int rn = bits (insn, 16, 19);
+
+  if (op1 == 0x10 && (op2 & 0x2) == 0x0 && (rn & 0xe) == 0x0)
+    return copy_unmodified (insn, "cps", dsc);
+  else if (op1 == 0x10 && op2 == 0x0 && (rn & 0xe) == 0x1)
+    return copy_unmodified (insn, "setend", dsc);
+  else if ((op1 & 0x60) == 0x20)
+    return copy_unmodified (insn, "neon dataproc", dsc);
+  else if ((op1 & 0x71) == 0x40)
+    return copy_unmodified (insn, "neon elt/struct load/store", dsc);
+  else if ((op1 & 0x77) == 0x41)
+    return copy_unmodified (insn, "unallocated mem hint", dsc);
+  else if ((op1 & 0x77) == 0x45)
+    return copy_preload (insn, regs, dsc);  /* pli.  */
+  else if ((op1 & 0x77) == 0x51)
+    {
+      if (rn != 0xf)
+        return copy_preload (insn, regs, dsc);  /* pld/pldw.  */
+      else
+        return copy_unpred (insn, dsc);
+    }
+  else if ((op1 & 0x77) == 0x55)
+    return copy_preload (insn, regs, dsc);  /* pld/pldw.  */
+  else if (op1 == 0x57)
+    switch (op2)
+      {
+      case 0x1: return copy_unmodified (insn, "clrex", dsc);
+      case 0x4: return copy_unmodified (insn, "dsb", dsc);
+      case 0x5: return copy_unmodified (insn, "dmb", dsc);
+      case 0x6: return copy_unmodified (insn, "isb", dsc);
+      default: return copy_unpred (insn, dsc);
+      }
+  else if ((op1 & 0x63) == 0x43)
+    return copy_unpred (insn, dsc);
+  else if ((op2 & 0x1) == 0x0)
+    switch (op1 & ~0x80)
+      {
+      case 0x61:
+	return copy_unmodified (insn, "unallocated mem hint", dsc);
+      case 0x65:
+	return copy_preload_reg (insn, regs, dsc);  /* pli reg.  */
+      case 0x71: case 0x75:
+	return copy_preload_reg (insn, regs, dsc);  /* pld/pldw reg.  */
+      case 0x63: case 0x67: case 0x73: case 0x77:
+	return copy_unpred (insn, dsc);
+      default:
+	return copy_undef (insn, dsc);
+      }
+  else
+    return copy_undef (insn, dsc);  /* Probably unreachable.  */
+}
+
+static int
+decode_unconditional (unsigned long insn, struct regcache *regs,
+		      struct displaced_step_closure *dsc)
+{
+  if (bit (insn, 27) == 0)
+    return decode_misc_memhint_neon (insn, regs, dsc);
+  /* Switch on bits: 0bxxxxx321xxx0xxxxxxxxxxxxxxxxxxxx.  */
+  else switch (((insn & 0x7000000) >> 23) | ((insn & 0x100000) >> 20))
+    {
+    case 0x0: case 0x2:
+      return copy_unmodified (insn, "srs", dsc);
+
+    case 0x1: case 0x3:
+      return copy_unmodified (insn, "rfe", dsc);
+
+    case 0x4: case 0x5: case 0x6: case 0x7:
+      return copy_b_bl_blx (insn, regs, dsc);
+
+    case 0x8:
+      switch ((insn & 0xe00000) >> 21)
+	{
+	case 0x1: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7:
+	  return copy_copro_load_store (insn, regs, dsc); /* stc/stc2.  */
+
+	case 0x2:
+	  return copy_unmodified (insn, "mcrr/mcrr2", dsc);
+
+	default:
+	  return copy_undef (insn, dsc);
+	}
+
+    case 0x9:
+      {
+        int rn_f = (bits (insn, 16, 19) == 0xf);
+	switch ((insn & 0xe00000) >> 21)
+	  {
+	  case 0x1: case 0x3:
+	    /* ldc/ldc2 imm (undefined for rn == pc).  */
+	    return rn_f ? copy_undef (insn, dsc)
+			: copy_copro_load_store (insn, regs, dsc);
+
+	  case 0x2:
+	    return copy_unmodified (insn, "mrrc/mrrc2", dsc);
+
+	  case 0x4: case 0x5: case 0x6: case 0x7:
+	    /* ldc/ldc2 lit (undefined for rn != pc).  */
+	    return rn_f ? copy_copro_load_store (insn, regs, dsc)
+			: copy_undef (insn, dsc);
+
+	  default:
+	    return copy_undef (insn, dsc);
+	  }
+      }
+
+    case 0xa:
+      return copy_unmodified (insn, "stc/stc2", dsc);
+
+    case 0xb:
+      if (bits (insn, 16, 19) == 0xf)
+        return copy_copro_load_store (insn, regs, dsc);  /* ldc/ldc2 lit.  */
+      else
+        return copy_undef (insn, dsc);
+
+    case 0xc:
+      if (bit (insn, 4))
+	return copy_unmodified (insn, "mcr/mcr2", dsc);
+      else
+	return copy_unmodified (insn, "cdp/cdp2", dsc);
+
+    case 0xd:
+      if (bit (insn, 4))
+        return copy_unmodified (insn, "mrc/mrc2", dsc);
+      else
+	return copy_unmodified (insn, "cdp/cdp2", dsc);
+
+    default:
+      return copy_undef (insn, dsc);
+    }
+}
+
+/* Decode miscellaneous instructions in dp/misc encoding space.  */
+
+static int
+decode_miscellaneous (unsigned long insn, struct regcache *regs,
+		      struct displaced_step_closure *dsc)
+{
+  unsigned int op2 = bits (insn, 4, 6);
+  unsigned int op = bits (insn, 21, 22);
+  unsigned int op1 = bits (insn, 16, 19);
+
+  switch (op2)
+    {
+    case 0x0:
+      return copy_unmodified (insn, "mrs/msr", dsc);
+
+    case 0x1:
+      if (op == 0x1)  /* bx.  */
+        return copy_bx_blx_reg (insn, regs, dsc);
+      else if (op == 0x3)
+        return copy_unmodified (insn, "clz", dsc);
+      else
+        return copy_undef (insn, dsc);
+
+    case 0x2:
+      if (op == 0x1)
+        return copy_unmodified (insn, "bxj", dsc);  /* Not really supported.  */
+      else
+        return copy_undef (insn, dsc);
+
+    case 0x3:
+      if (op == 0x1)
+        return copy_bx_blx_reg (insn, regs, dsc);  /* blx register.  */
+      else
+        return copy_undef (insn, dsc);
+
+    case 0x5:
+      return copy_unmodified (insn, "saturating add/sub", dsc);
+
+    case 0x7:
+      if (op == 0x1)
+        return copy_unmodified (insn, "bkpt", dsc);
+      else if (op == 0x3)
+        return copy_unmodified (insn, "smc", dsc);  /* Not really supported.  */
+
+    default:
+      return copy_undef (insn, dsc);
+    }
+}
+
+static int
+decode_dp_misc (unsigned long insn, struct regcache *regs,
+		struct displaced_step_closure *dsc)
+{
+  if (bit (insn, 25))
+    switch (bits (insn, 20, 24))
+      {
+      case 0x10:
+        return copy_unmodified (insn, "movw", dsc);
+
+      case 0x14:
+        return copy_unmodified (insn, "movt", dsc);
+
+      case 0x12: case 0x16:
+        return copy_unmodified (insn, "msr imm", dsc);
+
+      default:
+        return copy_dp_imm (insn, regs, dsc);
+      }
+  else
+    {
+      unsigned long op1 = bits (insn, 20, 24), op2 = bits (insn, 4, 7);
+
+      if ((op1 & 0x19) != 0x10 && (op2 & 0x1) == 0x0)
+        return copy_dp_reg (insn, regs, dsc);
+      else if ((op1 & 0x19) != 0x10 && (op2 & 0x9) == 0x1)
+        return copy_dp_shifted_reg (insn, regs, dsc);
+      else if ((op1 & 0x19) == 0x10 && (op2 & 0x8) == 0x0)
+        return decode_miscellaneous (insn, regs, dsc);
+      else if ((op1 & 0x19) == 0x10 && (op2 & 0x9) == 0x8)
+        return copy_unmodified (insn, "halfword mul/mla", dsc);
+      else if ((op1 & 0x10) == 0x00 && op2 == 0x9)
+        return copy_unmodified (insn, "mul/mla", dsc);
+      else if ((op1 & 0x10) == 0x10 && op2 == 0x9)
+        return copy_unmodified (insn, "synch", dsc);
+      else if (op2 == 0xb || (op2 & 0xd) == 0xd)
+        /* 2nd arg means "unpriveleged".  */
+        return copy_extra_ld_st (insn, (op1 & 0x12) == 0x02, regs, dsc);
+    }
+
+  /* Should be unreachable.  */
+  return 1;
+}
+
+static int
+decode_ld_st_word_ubyte (unsigned long insn, struct regcache *regs,
+			 struct displaced_step_closure *dsc)
+{
+  int a = bit (insn, 25), b = bit (insn, 4);
+  unsigned long op1 = bits (insn, 20, 24);
+  int rn_f = bits (insn, 16, 19) == 0xf;
+
+  if ((!a && (op1 & 0x05) == 0x00 && (op1 & 0x17) != 0x02)
+      || (a && (op1 & 0x05) == 0x00 && (op1 & 0x17) != 0x02 && !b))
+    return copy_ldr_str_ldrb_strb (insn, regs, dsc, 0, 0, 0);
+  else if ((!a && (op1 & 0x17) == 0x02)
+           || (a && (op1 & 0x17) == 0x02 && !b))
+    return copy_ldr_str_ldrb_strb (insn, regs, dsc, 0, 0, 1);
+  else if ((!a && (op1 & 0x05) == 0x01 && (op1 & 0x17) != 0x03)
+           || (a && (op1 & 0x05) == 0x01 && (op1 & 0x17) != 0x03 && !b))
+    return copy_ldr_str_ldrb_strb (insn, regs, dsc, 1, 0, 0);
+  else if ((!a && (op1 & 0x17) == 0x03)
+	   || (a && (op1 & 0x17) == 0x03 && !b))
+    return copy_ldr_str_ldrb_strb (insn, regs, dsc, 1, 0, 1);
+  else if ((!a && (op1 & 0x05) == 0x04 && (op1 & 0x17) != 0x06)
+           || (a && (op1 & 0x05) == 0x04 && (op1 & 0x17) != 0x06 && !b))
+    return copy_ldr_str_ldrb_strb (insn, regs, dsc, 0, 1, 0);
+  else if ((!a && (op1 & 0x17) == 0x06)
+	   || (a && (op1 & 0x17) == 0x06 && !b))
+    return copy_ldr_str_ldrb_strb (insn, regs, dsc, 0, 1, 1);
+  else if ((!a && (op1 & 0x05) == 0x05 && (op1 & 0x17) != 0x07)
+	   || (a && (op1 & 0x05) == 0x05 && (op1 & 0x17) != 0x07 && !b))
+    return copy_ldr_str_ldrb_strb (insn, regs, dsc, 1, 1, 0);
+  else if ((!a && (op1 & 0x17) == 0x07)
+	   || (a && (op1 & 0x17) == 0x07 && !b))
+    return copy_ldr_str_ldrb_strb (insn, regs, dsc, 1, 1, 1);
+
+  /* Should be unreachable.  */
+  return 1;
+}
+
+static int
+decode_media (unsigned long insn, struct displaced_step_closure *dsc)
+{
+  switch (bits (insn, 20, 24))
+    {
+    case 0x00: case 0x01: case 0x02: case 0x03:
+      return copy_unmodified (insn, "parallel add/sub signed", dsc);
+
+    case 0x04: case 0x05: case 0x06: case 0x07:
+      return copy_unmodified (insn, "parallel add/sub unsigned", dsc);
+
+    case 0x08: case 0x09: case 0x0a: case 0x0b:
+    case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+      return copy_unmodified (insn, "decode/pack/unpack/saturate/reverse", dsc);
+
+    case 0x18:
+      if (bits (insn, 5, 7) == 0)  /* op2.  */
+        {
+	  if (bits (insn, 12, 15) == 0xf)
+	    return copy_unmodified (insn, "usad8", dsc);
+	  else
+	    return copy_unmodified (insn, "usada8", dsc);
+	}
+      else
+        return copy_undef (insn, dsc);
+
+    case 0x1a: case 0x1b:
+      if (bits (insn, 5, 6) == 0x2)  /* op2[1:0].  */
+	return copy_unmodified (insn, "sbfx", dsc);
+      else
+        return copy_undef (insn, dsc);
+
+    case 0x1c: case 0x1d:
+      if (bits (insn, 5, 6) == 0x0)  /* op2[1:0].  */
+        {
+	  if (bits (insn, 0, 3) == 0xf)
+	    return copy_unmodified (insn, "bfc", dsc);
+	  else
+	    return copy_unmodified (insn, "bfi", dsc);
+	}
+      else
+        return copy_undef (insn, dsc);
+
+    case 0x1e: case 0x1f:
+      if (bits (insn, 5, 6) == 0x2)  /* op2[1:0].  */
+        return copy_unmodified (insn, "ubfx", dsc);
+      else
+        return copy_undef (insn, dsc);
+    }
+
+  /* Should be unreachable.  */
+  return 1;
+}
+
+static int
+decode_b_bl_ldmstm (unsigned long insn, struct regcache *regs,
+		    struct displaced_step_closure *dsc)
+{
+  if (bit (insn, 25))
+    return copy_b_bl_blx (insn, regs, dsc);
+  else
+    return copy_block_xfer (insn, regs, dsc);
+}
+
+static int
+decode_ext_reg_ld_st (unsigned long insn, struct regcache *regs,
+		      struct displaced_step_closure *dsc)
+{
+  unsigned int opcode = bits (insn, 20, 24);
+
+  switch (opcode)
+    {
+    case 0x04: case 0x05:  /* VFP/Neon mrrc/mcrr.  */
+      return copy_unmodified (insn, "vfp/neon mrrc/mcrr", dsc);
+
+    case 0x08: case 0x0a: case 0x0c: case 0x0e:
+    case 0x12: case 0x16:
+      return copy_unmodified (insn, "vfp/neon vstm/vpush", dsc);
+
+    case 0x09: case 0x0b: case 0x0d: case 0x0f:
+    case 0x13: case 0x17:
+      return copy_unmodified (insn, "vfp/neon vldm/vpop", dsc);
+
+    case 0x10: case 0x14: case 0x18: case 0x1c:  /* vstr.  */
+    case 0x11: case 0x15: case 0x19: case 0x1d:  /* vldr.  */
+      /* Note: no writeback for these instructions.  Bit 25 will always be
+	 zero though (via caller), so the following works OK.  */
+      return copy_copro_load_store (insn, regs, dsc);
+    }
+
+  /* Should be unreachable.  */
+  return 1;
+}
+
+static int
+decode_svc_copro (unsigned long insn, CORE_ADDR to, struct regcache *regs,
+		  struct displaced_step_closure *dsc)
+{
+  unsigned int op1 = bits (insn, 20, 25);
+  int op = bit (insn, 4);
+  unsigned int coproc = bits (insn, 8, 11);
+  unsigned int rn = bits (insn, 16, 19);
+
+  if ((op1 & 0x20) == 0x00 && (op1 & 0x3a) != 0x00 && (coproc & 0xe) == 0xa)
+    return decode_ext_reg_ld_st (insn, regs, dsc);
+  else if ((op1 & 0x21) == 0x00 && (op1 & 0x3a) != 0x00
+	   && (coproc & 0xe) != 0xa)
+    return copy_copro_load_store (insn, regs, dsc);  /* stc/stc2.  */
+  else if ((op1 & 0x21) == 0x01 && (op1 & 0x3a) != 0x00
+	   && (coproc & 0xe) != 0xa)
+    return copy_copro_load_store (insn, regs, dsc);  /* ldc/ldc2 imm/lit.  */
+  else if ((op1 & 0x3e) == 0x00)
+    return copy_undef (insn, dsc);
+  else if ((op1 & 0x3e) == 0x04 && (coproc & 0xe) == 0xa)
+    return copy_unmodified (insn, "neon 64bit xfer", dsc);
+  else if (op1 == 0x04 && (coproc & 0xe) != 0xa)
+    return copy_unmodified (insn, "mcrr/mcrr2", dsc);
+  else if (op1 == 0x05 && (coproc & 0xe) != 0xa)
+    return copy_unmodified (insn, "mrrc/mrrc2", dsc);
+  else if ((op1 & 0x30) == 0x20 && !op)
+    {
+      if ((coproc & 0xe) == 0xa)
+	return copy_unmodified (insn, "vfp dataproc", dsc);
+      else
+        return copy_unmodified (insn, "cdp/cdp2", dsc);
+    }
+  else if ((op1 & 0x30) == 0x20 && op)
+    return copy_unmodified (insn, "neon 8/16/32 bit xfer", dsc);
+  else if ((op1 & 0x31) == 0x20 && op && (coproc & 0xe) != 0xa)
+    return copy_unmodified (insn, "mcr/mcr2", dsc);
+  else if ((op1 & 0x31) == 0x21 && op && (coproc & 0xe) != 0xa)
+    return copy_unmodified (insn, "mrc/mrc2", dsc);
+  else if ((op1 & 0x30) == 0x30)
+    return copy_svc (insn, to, regs, dsc);
+  else
+    return copy_undef (insn, dsc);  /* Possibly unreachable.  */
+}
+
+static struct displaced_step_closure *
+arm_process_displaced_insn (unsigned long insn, CORE_ADDR from, CORE_ADDR to,
+			    struct regcache *regs)
+{
+  struct displaced_step_closure *dsc
+    = xmalloc (sizeof (struct displaced_step_closure));
+  int err = 0;
+
+  /* Most displaced instructions use a 1-instruction scratch space, so set this
+     here and override below if/when necessary.  */
+  dsc->numinsns = 1;
+  dsc->insn_addr = from;
+  dsc->cleanup = NULL;
+  dsc->wrote_to_pc = 0;
+
+  if ((insn & 0xf0000000) == 0xf0000000)
+    err = decode_unconditional (insn, regs, dsc);
+  else switch (((insn & 0x10) >> 4) | ((insn & 0xe000000) >> 24))
+    {
+    case 0x0: case 0x1: case 0x2: case 0x3:
+      err = decode_dp_misc (insn, regs, dsc);
+      break;
+
+    case 0x4: case 0x5: case 0x6:
+      err = decode_ld_st_word_ubyte (insn, regs, dsc);
+      break;
+
+    case 0x7:
+      err = decode_media (insn, dsc);
+      break;
+
+    case 0x8: case 0x9: case 0xa: case 0xb:
+      err = decode_b_bl_ldmstm (insn, regs, dsc);
+      break;
+
+    case 0xc: case 0xd: case 0xe: case 0xf:
+      err = decode_svc_copro (insn, to, regs, dsc);
+      break;
+    }
+
+  if (err)
+    internal_error (__FILE__, __LINE__,
+		    _("arm_process_displaced_insn: Instruction decode error"));
+
+  return dsc;
+}
+
+static struct displaced_step_closure *
+arm_catch_kernel_helper_return (CORE_ADDR from, CORE_ADDR to,
+				struct regcache *regs)
+{
+  struct displaced_step_closure *dsc
+    = xmalloc (sizeof (struct displaced_step_closure));
+
+  dsc->numinsns = 1;
+  dsc->insn_addr = from;
+  dsc->cleanup = &cleanup_kernel_helper_return;
+  /* Say we wrote to the PC, else cleanup will set PC to the next
+     instruction in the helper, which isn't helpful.  */
+  dsc->wrote_to_pc = 1;
+
+  /* Preparation: tmp[0] <- r14
+                  r14 <- <scratch space>+4
+		  *(<scratch space>+8) <- from
+     Insn: ldr pc, [r14, #4]
+     Cleanup: r14 <- tmp[0], pc <- tmp[0].  */
+
+  dsc->tmp[0] = displaced_read_reg (regs, from, ARM_LR_REGNUM);
+  displaced_write_reg (regs, dsc, ARM_LR_REGNUM, (ULONGEST) to + 4);
+  write_memory_unsigned_integer (to + 8, 4, from);
+
+  dsc->modinsn[0] = 0xe59ef004;  /* ldr pc, [lr, #4].  */
+
+  return dsc;
+}
+
+static void
+cleanup_kernel_helper_return (struct regcache *regs,
+			      struct displaced_step_closure *dsc)
+{
+  displaced_write_reg (regs, dsc, ARM_LR_REGNUM, dsc->tmp[0]);
+  displaced_write_reg (regs, dsc, ARM_PC_REGNUM, dsc->tmp[0]);
+}
+
+struct displaced_step_closure *
+arm_displaced_step_copy_insn (struct gdbarch *gdbarch,
+			      CORE_ADDR from, CORE_ADDR to,
+			      struct regcache *regs)
+{
+  struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+  const size_t len = 4;
+  gdb_byte *buf = xmalloc (len);
+  struct displaced_step_closure *dsc;
+  unsigned long insn;
+  int i;
+
+  /* A linux-specific hack.  Detect when we've entered (inaccessible by GDB)
+     kernel helpers, and stop at the return location.  */
+  if (gdbarch_osabi (gdbarch) == GDB_OSABI_LINUX && from > 0xffff0000)
+    {
+      if (debug_displaced)
+        fprintf_unfiltered (gdb_stdlog, "displaced: detected kernel helper "
+			    "at %.8lx\n", (unsigned long) from);
+
+      dsc = arm_catch_kernel_helper_return (from, to, regs);
+    }
+  else
+    {
+      insn = read_memory_unsigned_integer (from, len);
+
+      if (debug_displaced)
+	fprintf_unfiltered (gdb_stdlog, "displaced: stepping insn %.8lx "
+			    "at %.8lx\n", insn, (unsigned long) from);
+
+      dsc = arm_process_displaced_insn (insn, from, to, regs);
+    }
+
+  /* Poke modified instruction(s).  FIXME: Thumb, endianness.  */
+  for (i = 0; i < dsc->numinsns; i++)
+    {
+      if (debug_displaced)
+        fprintf_unfiltered (gdb_stdlog, "displaced: writing insn %.8lx at "
+			    "%.8lx\n", (unsigned long) dsc->modinsn[i],
+			    (unsigned long) to + i * 4);
+      write_memory_unsigned_integer (to + i * 4, 4, dsc->modinsn[i]);
+    }
+
+  /* Put breakpoint afterwards.  FIXME: Likewise.  */
+  write_memory (to + dsc->numinsns * 4, tdep->arm_breakpoint,
+		tdep->arm_breakpoint_size);
+
+  if (debug_displaced)
+    fprintf_unfiltered (gdb_stdlog, "displaced: copy 0x%s->0x%s: ",
+			paddr_nz (from), paddr_nz (to));
+
+  return dsc;
+}
+
+void
+arm_displaced_step_fixup (struct gdbarch *gdbarch,
+			  struct displaced_step_closure *dsc,
+			  CORE_ADDR from, CORE_ADDR to,
+			  struct regcache *regs)
+{
+  if (dsc->cleanup)
+    dsc->cleanup (regs, dsc);
+
+  if (!dsc->wrote_to_pc)
+    regcache_cooked_write_unsigned (regs, ARM_PC_REGNUM, dsc->insn_addr + 4);
+}
+
+
 #include "bfd-in2.h"
 #include "libcoff.h"
 
@@ -3252,6 +4702,10 @@ arm_gdbarch_init (struct gdbarch_info in
   /* On ARM targets char defaults to unsigned.  */
   set_gdbarch_char_signed (gdbarch, 0);
 
+  /* Note: for displaced stepping, this includes the breakpoint, and one word
+     of additional scratch space.  */
+  set_gdbarch_max_insn_length (gdbarch, 12);
+
   /* This should be low enough for everything.  */
   tdep->lowest_pc = 0x20;
   tdep->jb_pc = -1;	/* Longjump support not enabled by default.  */
--- .pc/arm-displaced-stepping/gdb/arm-tdep.h	2009-01-20 13:22:42.000000000 -0800
+++ gdb/arm-tdep.h	2009-01-20 13:24:00.000000000 -0800
@@ -178,6 +178,13 @@ CORE_ADDR arm_skip_stub (struct frame_in
 CORE_ADDR arm_get_next_pc (struct frame_info *, CORE_ADDR);
 int arm_software_single_step (struct frame_info *);
 
+extern struct displaced_step_closure *
+  arm_displaced_step_copy_insn (struct gdbarch *, CORE_ADDR, CORE_ADDR,
+				struct regcache *);
+extern void arm_displaced_step_fixup (struct gdbarch *,
+				      struct displaced_step_closure *,
+				      CORE_ADDR, CORE_ADDR, struct regcache *);
+
 /* Functions exported from armbsd-tdep.h.  */
 
 /* Return the appropriate register set for the core section identified
--- .pc/displaced-step-always/gdb/infrun.c	2009-01-20 13:23:02.000000000 -0800
+++ gdb/infrun.c	2009-01-20 13:23:34.000000000 -0800
@@ -825,6 +825,9 @@ displaced_step_fixup (ptid_t event_ptid,
      one now.  */
   while (displaced_step_request_queue)
     {
+      struct regcache *regcache;
+      struct gdbarch *gdbarch;
+
       struct displaced_step_request *head;
       ptid_t ptid;
       CORE_ADDR actual_pc;
@@ -847,8 +850,12 @@ displaced_step_fixup (ptid_t event_ptid,
 
 	  displaced_step_prepare (ptid);
 
+	  regcache = get_thread_regcache (ptid);
+	  gdbarch = get_regcache_arch (regcache);
+
 	  if (debug_displaced)
 	    {
+	      CORE_ADDR actual_pc = regcache_read_pc (regcache);
 	      gdb_byte buf[4];
 
 	      fprintf_unfiltered (gdb_stdlog, "displaced: run 0x%s: ",
@@ -857,7 +864,10 @@ displaced_step_fixup (ptid_t event_ptid,
 	      displaced_step_dump_bytes (gdb_stdlog, buf, sizeof (buf));
 	    }
 
-	  target_resume (ptid, 1, TARGET_SIGNAL_0);
+	  if (gdbarch_software_single_step_p (gdbarch))
+	    target_resume (ptid, 0, TARGET_SIGNAL_0);
+	  else
+	    target_resume (ptid, 1, TARGET_SIGNAL_0);
 
 	  /* Done, we're stepping a thread.  */
 	  break;
@@ -970,6 +980,7 @@ resume (int step, enum target_signal sig
   struct gdbarch *gdbarch = get_regcache_arch (regcache);
   struct thread_info *tp = inferior_thread ();
   CORE_ADDR pc = regcache_read_pc (regcache);
+  int hw_step = step;
 
   QUIT;
 
@@ -1014,7 +1025,8 @@ a command like `return' or `jump' to con
      comments in the handle_inferior event for dealing with 'random
      signals' explain what we do instead.  */
   if (use_displaced_stepping (gdbarch)
-      && tp->trap_expected
+      && (tp->trap_expected
+	  || (step && gdbarch_software_single_step_p (gdbarch)))
       && sig == TARGET_SIGNAL_0)
     {
       if (!displaced_step_prepare (inferior_ptid))
@@ -1033,11 +1045,13 @@ a command like `return' or `jump' to con
 
   if (step && gdbarch_software_single_step_p (gdbarch))
     {
+      if (use_displaced_stepping (gdbarch))
+	hw_step = 0;
       /* Do it the hard way, w/temp breakpoints */
-      if (gdbarch_software_single_step (gdbarch, get_current_frame ()))
+      else if (gdbarch_software_single_step (gdbarch, get_current_frame ()))
         {
           /* ...and don't ask hardware to do it.  */
-          step = 0;
+          hw_step = 0;
           /* and do not pull these breakpoints until after a `wait' in
           `wait_for_inferior' */
           singlestep_breakpoints_inserted_p = 1;
@@ -1085,7 +1099,7 @@ a command like `return' or `jump' to con
       /* If STEP is set, it's a request to use hardware stepping
 	 facilities.  But in that case, we should never
 	 use singlestep breakpoint.  */
-      gdb_assert (!(singlestep_breakpoints_inserted_p && step));
+      gdb_assert (!(singlestep_breakpoints_inserted_p && hw_step));
 
       if (singlestep_breakpoints_inserted_p
 	  && stepping_past_singlestep_breakpoint)
@@ -1139,13 +1153,14 @@ a command like `return' or `jump' to con
 	  /* Most targets can step a breakpoint instruction, thus
 	     executing it normally.  But if this one cannot, just
 	     continue and we will hit it anyway.  */
-	  if (step && breakpoint_inserted_here_p (pc))
-	    step = 0;
+	  if (hw_step && breakpoint_inserted_here_p (pc))
+	    hw_step = 0;
 	}
 
       if (debug_displaced
           && use_displaced_stepping (gdbarch)
-          && tp->trap_expected)
+          && (tp->trap_expected
+	      || (step && gdbarch_software_single_step_p (gdbarch))))
         {
 	  struct regcache *resume_regcache = get_thread_regcache (resume_ptid);
           CORE_ADDR actual_pc = regcache_read_pc (resume_regcache);
@@ -1161,7 +1176,7 @@ a command like `return' or `jump' to con
 	 happens to apply to another thread.  */
       tp->stop_signal = TARGET_SIGNAL_0;
 
-      target_resume (resume_ptid, step, sig);
+      target_resume (resume_ptid, hw_step, sig);
     }
 
   discard_cleanups (old_cleanups);

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]