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] MIPS: Avoid breakpoints in branch delay slots


Hi,

 This change makes GDB avoid placing breakpoints in a branch delay slot.  
The rationale for this change is explained within the patch itself in the 
comment within the mips_adjust_breakpoint_address's body, so I won't 
repeat it here; see below.

 Regression tested successfully for mips-sde-elf (MIPS32 and MIPS16 
multilibs, big- and little-endian each) and mips-linux-gnu.  OK to apply?

2011-11-22  Chris Dearman  <chris@mips.com>
            Nathan Froyd  <froydnj@codesourcery.com>
            Maciej W. Rozycki  <macro@codesourcery.com>

	gdb/
	* mips-tdep.c (mips32_instruction_has_delay_slot): New function.
	(mips16_instruction_has_delay_slot): Likewise.
	(mips_segment_boundary): Likewise.
	(mips_adjust_breakpoint_address): Likewise.
	(mips_gdbarch_init): Use mips_adjust_breakpoint_address.

  Maciej

gdb-mips-breakpoint-adjust.diff
Index: gdb-fsf-trunk-quilt/gdb/mips-tdep.c
===================================================================
--- gdb-fsf-trunk-quilt.orig/gdb/mips-tdep.c	2011-11-23 02:48:41.000000000 +0000
+++ gdb-fsf-trunk-quilt/gdb/mips-tdep.c	2011-11-23 12:51:00.525461787 +0000
@@ -5351,6 +5351,223 @@ mips_breakpoint_from_pc (struct gdbarch 
     }
 }
 
+/* Return non-zero if the ADDR instruction has a branch delay slot
+   (i.e. it is a jump or branch instruction).  This function is based
+   on mips32_next_pc.  */
+
+static int
+mips32_instruction_has_delay_slot (struct gdbarch *gdbarch, CORE_ADDR addr)
+{
+  gdb_byte buf[MIPS_INSN32_SIZE];
+  unsigned long inst;
+  int status;
+  int op;
+
+  status = target_read_memory (addr, buf, MIPS_INSN32_SIZE);
+  if (status)
+    return 0;
+
+  inst = mips_fetch_instruction (gdbarch, addr);
+  op = itype_op (inst);
+  if ((inst & 0xe0000000) != 0)
+    return (op >> 2 == 5)	/* BEQL, BNEL, BLEZL, BGTZL: bits 0101xx */
+	   || (op == 29)	/* JALX: bits 011101 */
+	   || (op == 17 && itype_rs (inst) == 8);
+				/* BC1F, BC1FL, BC1T, BC1TL: 010001 01000 */
+  else
+    {
+      switch (op & 0x07)	/* extract bits 28,27,26 */
+	{
+	case 0:			/* SPECIAL */
+	  op = rtype_funct (inst);
+	  return (op == 8)	/* JR */
+		 || (op == 9);	/* JALR */
+	  break;		/* end SPECIAL */
+	case 1:			/* REGIMM */
+	  op = itype_rt (inst);	/* branch condition */
+	  return (op & 0xc) == 0;
+				/* BLTZ, BLTZL, BGEZ, BGEZL: bits 000xx */
+				/* BLTZAL, BLTZALL, BGEZAL, BGEZALL: 100xx */
+	  break;		/* end REGIMM */
+	default:		/* J, JAL, BEQ, BNE, BLEZ, BGTZ */
+	  return 1;
+	  break;
+	}
+    }
+}
+
+static int
+mips16_instruction_has_delay_slot (struct gdbarch *gdbarch, CORE_ADDR addr,
+				   int mustbe32)
+{
+  gdb_byte buf[MIPS_INSN16_SIZE];
+  unsigned short inst;
+  int status;
+
+  status = target_read_memory (addr, buf, MIPS_INSN16_SIZE);
+  if (status)
+    return 0;
+
+  inst = mips_fetch_instruction (gdbarch, addr);
+  if (!mustbe32)
+    return (inst & 0xf89f) == 0xe800;	/* jr/jalr (16-bit instruction)  */
+  return (inst & 0xf800) == 0x1800;	/* jal/jalx (32-bit instruction)  */
+}
+
+static CORE_ADDR
+mips_segment_boundary (CORE_ADDR bpaddr)
+{
+#ifdef BFD64
+  switch ((int) (bpaddr >> 62) & 0x3)
+    {
+    case 0x3:				/* xkseg  */
+      if (bpaddr == (bfd_signed_vma) (int) bpaddr)
+	return bpaddr & ~0x1fffffffLL;	/* 32-bit compatibility segment  */
+      break;
+    case 0x2:				/* xkphys  */
+      return bpaddr & 0xf800000000000000LL;
+    case 0x1:				/* xksseg  */
+    case 0x0:				/* xkuseg/kuseg  */
+      break;
+    }
+  return bpaddr & 0xc000000000000000LL;
+#else
+  if (bpaddr & (CORE_ADDR) 0x80000000)	/* kernel segment  */
+    return bpaddr & ~(CORE_ADDR) 0x1fffffff;
+  return 0x00000000;			/* user segment  */
+#endif
+}
+
+static CORE_ADDR
+mips_adjust_breakpoint_address (struct gdbarch *gdbarch, CORE_ADDR bpaddr)
+{
+  CORE_ADDR prev_addr, next_addr;
+  CORE_ADDR boundary;
+  CORE_ADDR func_addr;
+
+  /* If a breakpoint is set on the instruction in a branch delay slot,
+     GDB gets confused.  When the breakpoint is hit, the PC isn't on
+     the instruction in the branch delay slot, the PC will point to
+     the branch instruction.  Since the PC doesn't match any known
+     breakpoints, GDB reports a trap exception.
+
+     There are two possible fixes for this problem.
+
+     1) When the breakpoint gets hit, see if the BD bit is set in the
+     Cause register (which indicates the last exception occurred in a
+     branch delay slot).  If the BD bit is set, fix the PC to point to
+     the instruction in the branch delay slot.
+
+     2) When the user sets the breakpoint, don't allow him to set the
+     breakpoint on the instruction in the branch delay slot.  Instead
+     move the breakpoint to the branch instruction (which will have
+     the same result).
+
+     The problem with the first solution is that if the user then
+     single-steps the processor, the branch instruction will get
+     skipped (since GDB thinks the PC is on the instruction in the
+     branch delay slot).
+
+     So, we'll use the second solution.  To do this we need to know if
+     the instruction we're trying to set the breakpoint on is in the
+     branch delay slot.  */
+
+  boundary = mips_segment_boundary (bpaddr);
+
+  /* Make sure we don't scan back before the beginning of the current
+     function, since we may fetch constant data or insns that look like
+     a jump.  Of course we might do that anyway if the compiler has
+     moved constants inline. :-(  */
+  if (find_pc_partial_function (bpaddr, NULL, &func_addr, NULL)
+      && func_addr > boundary && func_addr <= bpaddr)
+    boundary = func_addr;
+
+  if (!mips_pc_is_mips16 (bpaddr))
+    {
+      if (bpaddr == boundary)
+	return bpaddr;
+
+      /* If the previous instruction has a branch delay slot, we have
+         to move the breakpoint to the branch instruction. */
+      prev_addr = bpaddr - 4;
+      if (mips32_instruction_has_delay_slot (gdbarch, prev_addr))
+	{
+	  bpaddr = prev_addr;
+	}
+    }
+  else
+    {
+      struct minimal_symbol *sym;
+      CORE_ADDR addr, jmpaddr;
+      int i;
+
+      boundary = unmake_mips16_addr (boundary);
+
+      /* The only MIPS16 instructions with delay slots are jal, jalr
+         and jr.  An absolute jal is always 4 bytes long, so try for
+         that first, then try the 2 byte jalr/jal.
+         XXX We have to assume that bpaddr is not the second half of
+         an extended instruction.  */
+
+      jmpaddr = 0;
+      addr = bpaddr;
+      for (i = 1; i < 4; i++)
+	{
+	  if (unmake_mips16_addr (addr) == boundary)
+	    break;
+	  addr -= 2;
+	  if (i == 1 && mips16_instruction_has_delay_slot (gdbarch, addr, 0))
+	    {
+	      /* Looks like a jr/jalr at [target-1], but it could be
+	         the second word of a previous jal, so record it and
+	         check back one more.  */
+	      jmpaddr = addr;
+	    }
+	  else if (i > 1
+		   && mips16_instruction_has_delay_slot (gdbarch, addr, 1))
+	    {
+	      if (i == 2)
+		/* Looks like a jal at [target-2], but it could also
+		   be the second word of a previous jal, record it,
+		   and check back one more.  */
+		jmpaddr = addr;
+	      else
+		/* Looks like a jal at [target-3], so any previously
+		   recorded jal or jr must be wrong, because:
+
+		   >-3: jal
+		    -2: jal-ext (can't be jal)
+		    -1: bdslot (can't be jr)
+		     0: target insn
+
+		   Of course it could be another jal-ext which looks
+		   like a jal, but in that case we'd have broken out
+		   of this loop at [target-2]:
+
+		    -4: jal
+		   >-3: jal-ext
+		    -2: bdslot (can't be jmp)
+		    -1: jalr/jr
+		     0: target insn  */
+		jmpaddr = 0;
+	    }
+	  else
+	    {
+	      /* Not a jump instruction: if we're at [target-1] this
+	         could be the second word of a jal, so continue;
+	         otherwise we're done.  */
+	      if (i > 1)
+		break;
+	    }
+	}
+
+      if (jmpaddr)
+	bpaddr = jmpaddr;
+    }
+
+  return bpaddr;
+}
+
 /* If PC is in a mips16 call or return stub, return the address of the target
    PC, which is either the callee or the caller.  There are several
    cases which must be handled:
@@ -6337,6 +6554,8 @@ mips_gdbarch_init (struct gdbarch_info i
 
   set_gdbarch_inner_than (gdbarch, core_addr_lessthan);
   set_gdbarch_breakpoint_from_pc (gdbarch, mips_breakpoint_from_pc);
+  set_gdbarch_adjust_breakpoint_address (gdbarch,
+					 mips_adjust_breakpoint_address);
 
   set_gdbarch_skip_prologue (gdbarch, mips_skip_prologue);
 


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