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]

Support for single-stepping Thumb-2 programs


This patch adds software single step decoding for Thumb-2.  Mostly
this is straightforward; there are a couple of new 16-bit instructions
(CBNZ, CBZ), a host of new 32-bit instructions we have to match, plus
the default for 32-bit instructions is to skip 32 bits instead of 16.

There's also support for the IT instruction.  IT is an alternate
form of conditional execution; you write this:

  ITF   EQ
  MOVEQ R0, R1
  MOVNE R0, R2

The IT instruction sets up flags in CPSR, which cause the MOVEQ and
MOVNE (which, in Thumb-2 mode, assemble to ordinary MOVs) to be
executed if the conditions indicate.  The T is either "Then" or
"True", and the F is "False".

It's not too complicated to figure out from this whether we're in an
IT block.  Unfortunately it's quite hard to single step at this point.
The condition codes are allowed to change during the block, so an
instruction can alter whether the next instruction executes.  Also, a
block might end with an indirect jump.  It's difficult to figure out
where we're going to end up after an IT instruction or any instruction
inside an IT block.

There's an instruction, BKPT, with the nice property of being executed
unconditionally even inside an IT block.  Unfortunately we can't use
it for breakpoints on GNU/Linux; it would trap an attached hardware
debugger.  The undefined instruction we currently use does not have
this property.  Also, it's 16-bit; setting a 16-bit breakpoint on a
32-bit instruction inside an IT block is trouble, because if the
breakpoint is skipped we stop in the middle of an instruction.

So the patch does not allow single stepping inside an IT block.  Not
explicitly mentioned in the patch is that it also doesn't address
setting breakpoints inside an IT block, which has the same problem.

My best plan to date to handle stepping is to reuse displaced stepping
for this somehow.  Fill the rest of the IT block with breakpoints or
nops to simulate just the instruction we're interested in.  As for
user set breakpoints, I think we'll have to adjust them to point at
the IT instruction instead.

I haven't implemented any of that yet.  This is generally useful
as-is, so I'm submitting it without those potential improvements.

The patch also includes a followup to my previous patch, to handle
Thumb entry points; we must mark the return address as Thumb, or else
two things go wrong.  We'll try to interpret a Thumb breakpoint
instruction as half of an ARM instruction and we'll fault on M-profile
cores which do not implement ARM mode.

This has been in our tree for a while and thoroughly tested.  I
retested on ARM GNU/Linux and will check it in.

-- 
Daniel Jacobowitz
CodeSourcery

2009-07-28  Daniel Jacobowitz  <dan@codesourcery.com>

	* arm-tdep.c (arm_push_dummy_call): Set the low bit of LR for
	a Thumb entry point.
	(thumb_get_next_pc): Handle Thumb-2 and ARM v6 instructions.  Refuse
	to single step into IT blocks.

---
 gdb/arm-tdep.c |  212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 204 insertions(+), 8 deletions(-)

Index: gdb-mainline/gdb/arm-tdep.c
===================================================================
--- gdb-mainline.orig/gdb/arm-tdep.c	2009-07-23 11:59:57.000000000 -0700
+++ gdb-mainline/gdb/arm-tdep.c	2009-07-23 12:51:57.000000000 -0700
@@ -1391,7 +1391,8 @@ arm_push_dummy_call (struct gdbarch *gdb
 
   /* Set the return address.  For the ARM, the return breakpoint is
      always at BP_ADDR.  */
-  /* XXX Fix for Thumb.  */
+  if (arm_pc_is_thumb (bp_addr))
+    bp_addr |= 1;
   regcache_cooked_write_unsigned (regcache, ARM_LR_REGNUM, bp_addr);
 
   /* Walk through the list of args and determine how large a temporary
@@ -1835,9 +1836,43 @@ thumb_get_next_pc (struct frame_info *fr
   unsigned short inst1;
   CORE_ADDR nextpc = pc + 2;		/* default is next instruction */
   unsigned long offset;
+  ULONGEST status, it;
 
   inst1 = read_memory_unsigned_integer (pc, 2, byte_order_for_code);
 
+  /* Thumb-2 conditional execution support.  There are eight bits in
+     the CPSR which describe conditional execution state.  Once
+     reconstructed (they're in a funny order), the low five bits
+     describe the low bit of the condition for each instruction and
+     how many instructions remain.  The high three bits describe the
+     base condition.  One of the low four bits will be set if an IT
+     block is active.  These bits read as zero on earlier
+     processors.  */
+  status = get_frame_register_unsigned (frame, ARM_PS_REGNUM);
+  it = ((status >> 8) & 0xfc) | ((status >> 25) & 0x3);
+
+  /* On GNU/Linux, where this routine is used, we use an undefined
+     instruction as a breakpoint.  Unlike BKPT, IT can disable execution
+     of the undefined instruction.  So we might miss the breakpoint!  */
+  if ((inst1 & 0xff00) == 0xbf00 || (it & 0x0f))
+    error (_("Stepping through Thumb-2 IT blocks is not yet supported"));
+
+  if (it & 0x0f)
+    {
+      /* We are in a conditional block.  Check the condition.  */
+      int cond = it >> 4;
+
+      if (! condition_true (cond, status))
+	{
+	  /* Advance to the next instruction.  All the 32-bit
+	     instructions share a common prefix.  */
+	  if ((inst1 & 0xe000) == 0xe000 && (inst1 & 0x1800) != 0)
+	    return pc + 4;
+	  else
+	    return pc + 2;
+	}
+    }
+
   if ((inst1 & 0xff00) == 0xbd00)	/* pop {rlist, pc} */
     {
       CORE_ADDR sp;
@@ -1853,7 +1888,6 @@ thumb_get_next_pc (struct frame_info *fr
     }
   else if ((inst1 & 0xf000) == 0xd000)	/* conditional branch */
     {
-      unsigned long status = get_frame_register_unsigned (frame, ARM_PS_REGNUM);
       unsigned long cond = bits (inst1, 8, 11);
       if (cond != 0x0f && condition_true (cond, status))    /* 0x0f = SWI */
 	nextpc = pc_val + (sbits (inst1, 0, 7) << 1);
@@ -1862,15 +1896,166 @@ thumb_get_next_pc (struct frame_info *fr
     {
       nextpc = pc_val + (sbits (inst1, 0, 10) << 1);
     }
-  else if ((inst1 & 0xf800) == 0xf000)	/* long branch with link, and blx */
+  else if ((inst1 & 0xe000) == 0xe000) /* 32-bit instruction */
     {
       unsigned short inst2;
       inst2 = read_memory_unsigned_integer (pc + 2, 2, byte_order_for_code);
-      offset = (sbits (inst1, 0, 10) << 12) + (bits (inst2, 0, 10) << 1);
-      nextpc = pc_val + offset;
-      /* For BLX make sure to clear the low bits.  */
-      if (bits (inst2, 11, 12) == 1)
-	nextpc = nextpc & 0xfffffffc;
+
+      /* Default to the next instruction.  */
+      nextpc = pc + 4;
+
+      if ((inst1 & 0xf800) == 0xf000 && (inst2 & 0x8000) == 0x8000)
+	{
+	  /* Branches and miscellaneous control instructions.  */
+
+	  if ((inst2 & 0x1000) != 0 || (inst2 & 0xd001) == 0xc000)
+	    {
+	      /* B, BL, BLX.  */
+	      int j1, j2, imm1, imm2;
+
+	      imm1 = sbits (inst1, 0, 10);
+	      imm2 = bits (inst2, 0, 10);
+	      j1 = bit (inst2, 13);
+	      j2 = bit (inst2, 11);
+
+	      offset = ((imm1 << 12) + (imm2 << 1));
+	      offset ^= ((!j2) << 22) | ((!j1) << 23);
+
+	      nextpc = pc_val + offset;
+	      /* For BLX make sure to clear the low bits.  */
+	      if (bit (inst2, 12) == 0)
+		nextpc = nextpc & 0xfffffffc;
+	    }
+	  else if (inst1 == 0xf3de && (inst2 & 0xff00) == 0x3f00)
+	    {
+	      /* SUBS PC, LR, #imm8.  */
+	      nextpc = get_frame_register_unsigned (frame, ARM_LR_REGNUM);
+	      nextpc -= inst2 & 0x00ff;
+	    }
+	  else if ((inst2 & 0xd000) == 0xc000 && (inst1 & 0x0380) != 0x0380)
+	    {
+	      /* Conditional branch.  */
+	      if (condition_true (bits (inst1, 6, 9), status))
+		{
+		  int sign, j1, j2, imm1, imm2;
+
+		  sign = sbits (inst1, 10, 10);
+		  imm1 = bits (inst1, 0, 5);
+		  imm2 = bits (inst2, 0, 10);
+		  j1 = bit (inst2, 13);
+		  j2 = bit (inst2, 11);
+
+		  offset = (sign << 20) + (j2 << 19) + (j1 << 18);
+		  offset += (imm1 << 12) + (imm2 << 1);
+
+		  nextpc = pc_val + offset;
+		}
+	    }
+	}
+      else if ((inst1 & 0xfe50) == 0xe810)
+	{
+	  /* Load multiple or RFE.  */
+	  int rn, offset, load_pc = 1;
+
+	  rn = bits (inst1, 0, 3);
+	  if (bit (inst1, 7) && !bit (inst1, 8))
+	    {
+	      /* LDMIA or POP */
+	      if (!bit (inst2, 15))
+		load_pc = 0;
+	      offset = bitcount (inst2) * 4 - 4;
+	    }
+	  else if (!bit (inst1, 7) && bit (inst1, 8))
+	    {
+	      /* LDMDB */
+	      if (!bit (inst2, 15))
+		load_pc = 0;
+	      offset = -4;
+	    }
+	  else if (bit (inst1, 7) && bit (inst1, 8))
+	    {
+	      /* RFEIA */
+	      offset = 0;
+	    }
+	  else if (!bit (inst1, 7) && !bit (inst1, 8))
+	    {
+	      /* RFEDB */
+	      offset = -8;
+	    }
+	  else
+	    load_pc = 0;
+
+	  if (load_pc)
+	    {
+	      CORE_ADDR addr = get_frame_register_unsigned (frame, rn);
+	      nextpc = get_frame_memory_unsigned (frame, addr + offset, 4);
+	    }
+	}
+      else if ((inst1 & 0xffef) == 0xea4f && (inst2 & 0xfff0) == 0x0f00)
+	{
+	  /* MOV PC or MOVS PC.  */
+	  nextpc = get_frame_register_unsigned (frame, bits (inst2, 0, 3));
+	}
+      else if ((inst1 & 0xff70) == 0xf850 && (inst2 & 0xf000) == 0xf000)
+	{
+	  /* LDR PC.  */
+	  CORE_ADDR base;
+	  int rn, load_pc = 1;
+
+	  rn = bits (inst1, 0, 3);
+	  base = get_frame_register_unsigned (frame, rn);
+	  if (rn == 15)
+	    {
+	      base = (base + 4) & ~(CORE_ADDR) 0x3;
+	      if (bit (inst1, 7))
+		base += bits (inst2, 0, 11);
+	      else
+		base -= bits (inst2, 0, 11);
+	    }
+	  else if (bit (inst1, 7))
+	    base += bits (inst2, 0, 11);
+	  else if (bit (inst2, 11))
+	    {
+	      if (bit (inst2, 10))
+		{
+		  if (bit (inst2, 9))
+		    base += bits (inst2, 0, 7);
+		  else
+		    base -= bits (inst2, 0, 7);
+		}
+	    }
+	  else if ((inst2 & 0x0fc0) == 0x0000)
+	    {
+	      int shift = bits (inst2, 4, 5), rm = bits (inst2, 0, 3);
+	      base += get_frame_register_unsigned (frame, rm) << shift;
+	    }
+	  else
+	    /* Reserved.  */
+	    load_pc = 0;
+
+	  if (load_pc)
+	    nextpc = get_frame_memory_unsigned (frame, base, 4);
+	}
+      else if ((inst1 & 0xfff0) == 0xe8d0 && (inst2 & 0xfff0) == 0xf000)
+	{
+	  /* TBB.  */
+	  CORE_ADDR table, offset, length;
+
+	  table = get_frame_register_unsigned (frame, bits (inst1, 0, 3));
+	  offset = get_frame_register_unsigned (frame, bits (inst2, 0, 3));
+	  length = 2 * get_frame_memory_unsigned (frame, table + offset, 1);
+	  nextpc = pc_val + length;
+	}
+      else if ((inst1 & 0xfff0) == 0xe8d0 && (inst2 & 0xfff0) == 0xf000)
+	{
+	  /* TBH.  */
+	  CORE_ADDR table, offset, length;
+
+	  table = get_frame_register_unsigned (frame, bits (inst1, 0, 3));
+	  offset = 2 * get_frame_register_unsigned (frame, bits (inst2, 0, 3));
+	  length = 2 * get_frame_memory_unsigned (frame, table + offset, 2);
+	  nextpc = pc_val + length;
+	}
     }
   else if ((inst1 & 0xff00) == 0x4700)	/* bx REG, blx REG */
     {
@@ -1883,6 +2068,17 @@ thumb_get_next_pc (struct frame_info *fr
       if (nextpc == pc)
 	error (_("Infinite loop detected"));
     }
+  else if ((inst1 & 0xf500) == 0xb100)
+    {
+      /* CBNZ or CBZ.  */
+      int imm = (bit (inst1, 9) << 6) + (bits (inst1, 3, 7) << 1);
+      ULONGEST reg = get_frame_register_unsigned (frame, bits (inst1, 0, 2));
+
+      if (bit (inst1, 11) && reg != 0)
+	nextpc = pc_val + imm;
+      else if (!bit (inst1, 11) && reg == 0)
+	nextpc = pc_val + imm;
+    }
 
   return nextpc;
 }


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