This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[PATCH] MIPS: Avoid breakpoints in branch delay slots
- From: "Maciej W. Rozycki" <macro at codesourcery dot com>
- To: <gdb-patches at sourceware dot org>
- Cc: Chris Dearman <chris at mips dot com>
- Date: Wed, 23 Nov 2011 18:10:13 +0000
- Subject: [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);