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]

mips-tdep.c: Adjust breakpoints in branch delay slots


Hello,

 This is a change that makes breakpoints requested in a branch or jump 
delay slot be moved to the preceding instruction.  This removes a 
confusion that arises when such a breakpoint is hit and the resulting 
message like this:

(gdb) break *0x00400670
Breakpoint 1 at 0x400670: file foo.c, line 12.
(gdb) run
Starting program: .../foo

Program received signal SIGTRAP, Trace/breakpoint trap.
main (argc=1, argv=0x7fd7c874) at foo.c:12
12      }
(gdb) x /2i $pc
0x40066c <main+12>:     jr      ra
0x400670 <main+16>:     move    v0,zero

 This change has been tested natively for mips-unknown-linux-gnu and 
remotely for mipsisa32-sde-elf, using mips-sim-sde32/-EB, 
mips-sim-sde32/-mips16/-EB, mips-sim-sde32/-EL and 
mips-sim-sde32/-mips16/-EL as the targets, with no regressions.

2007-05-24  Chris Dearman  <chris@mips.com>
            Maciej W. Rozycki  <macro@mips.com>

	* 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.

 OK to apply?

  Maciej

12271-4
Index: binutils-quilt/src/gdb/mips-tdep.c
===================================================================
--- binutils-quilt.orig/src/gdb/mips-tdep.c	2007-05-23 17:50:36.000000000 +0100
+++ binutils-quilt/src/gdb/mips-tdep.c	2007-05-24 17:58:17.000000000 +0100
@@ -4817,6 +4817,186 @@
     }
 }
 
+/* Return non-zero if the ADDR instruction has a branch delay slot
+   (i.e. it is a jump or branch instruction).  This function was based
+   on mips32_next_pc().  */
+
+static int
+mips32_instruction_has_delay_slot (CORE_ADDR addr)
+{
+  gdb_byte buf[MIPS_INSN32_SIZE];
+  int status;
+
+  status = read_memory_nobpt (addr, buf, MIPS_INSN32_SIZE);
+  return !status && mips32_next_pc (addr) != addr + 4;
+}
+
+static int
+mips16_instruction_has_delay_slot (CORE_ADDR addr, int mustbe32)
+{
+  gdb_byte buf[MIPS_INSN16_SIZE];
+  unsigned short inst;
+  int status;
+
+  status = read_memory_nobpt (addr, buf, MIPS_INSN16_SIZE);
+  if (status)
+    return 0;
+
+  inst = mips_fetch_instruction (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 0x0000000;			/* 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;
+
+  /* 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);
+
+  if (!is_mips16_addr (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 (prev_addr))
+	{
+	  bpaddr = prev_addr;
+	}
+    }
+  else
+    {
+      struct minimal_symbol *sym;
+      CORE_ADDR addr, jmpaddr;
+      int i;
+
+      /* 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.  */
+
+      /* Make sure we don't scan back before the beginning of the
+         current function, since we may fetch constant data or MIPS32
+         insns that looks like a MIPS16 jump.  Of course we might do
+         that anyway if the compiler has moved constants inline. :-(  */
+      if (find_pc_partial_function (bpaddr, NULL, &addr, NULL)
+	  && addr > boundary && addr < bpaddr)
+	  boundary = addr & ~1;
+
+      jmpaddr = 0;
+      addr = bpaddr;
+      for (i = 1; i < 4; i++)
+	{
+	  if ((addr & ~1) == boundary)
+	    break;
+	  addr -= 2;
+	  if (i == 1 && mips16_instruction_has_delay_slot (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 (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:
@@ -5514,6 +5694,8 @@
 
   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]