summaryrefslogtreecommitdiff
path: root/gdb/mips-tdep.c
diff options
context:
space:
mode:
authorMaciej W. Rozycki <macro@linux-mips.org>2012-02-27 23:05:40 +0000
committerMaciej W. Rozycki <macro@linux-mips.org>2012-02-27 23:05:40 +0000
commit022a761ce1c0ec0db2c06eb920844d39c7c118d5 (patch)
tree47b0f170dbd76778df4e066d0c2d46dc086f39dd /gdb/mips-tdep.c
parentaa3135fbe1b7939a857516bb13d5970bec9769f4 (diff)
downloadgdb-022a761ce1c0ec0db2c06eb920844d39c7c118d5.tar.gz
* 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.
Diffstat (limited to 'gdb/mips-tdep.c')
-rw-r--r--gdb/mips-tdep.c232
1 files changed, 232 insertions, 0 deletions
diff --git a/gdb/mips-tdep.c b/gdb/mips-tdep.c
index a670cd38610..67ead3c0d2d 100644
--- a/gdb/mips-tdep.c
+++ b/gdb/mips-tdep.c
@@ -5293,6 +5293,236 @@ mips_breakpoint_from_pc (struct gdbarch *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;
+ }
+}
+
+/* Return non-zero if the ADDR instruction, which must be a 32-bit
+ instruction if MUSTBE32 is set or can be any instruction otherwise,
+ has a branch delay slot (i.e. it is a non-compact jump instruction). */
+
+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) */
+}
+
+/* Calculate the starting address of the MIPS memory segment BPADDR is in.
+ This assumes KSSEG exists. */
+
+static CORE_ADDR
+mips_segment_boundary (CORE_ADDR bpaddr)
+{
+ CORE_ADDR mask = CORE_ADDR_MAX;
+ int segsize;
+
+ if (sizeof (CORE_ADDR) == 8)
+ /* Get the topmost two bits of bpaddr in a 32-bit safe manner (avoid
+ a compiler warning produced where CORE_ADDR is a 32-bit type even
+ though in that case this is dead code). */
+ switch (bpaddr >> ((sizeof (CORE_ADDR) << 3) - 2) & 3)
+ {
+ case 3:
+ if (bpaddr == (bfd_signed_vma) (int32_t) bpaddr)
+ segsize = 29; /* 32-bit compatibility segment */
+ else
+ segsize = 62; /* xkseg */
+ break;
+ case 2: /* xkphys */
+ segsize = 59;
+ break;
+ default: /* xksseg (1), xkuseg/kuseg (0) */
+ segsize = 62;
+ break;
+ }
+ else if (bpaddr & 0x80000000) /* kernel segment */
+ segsize = 29;
+ else
+ segsize = 31; /* user segment */
+ mask <<= segsize;
+ return bpaddr & mask;
+}
+
+/* Move the breakpoint at BPADDR out of any branch delay slot by shifting
+ it backwards if necessary. Return the address of the new location. */
+
+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, JALX,
+ JALR and JR. An absolute JAL/JALX is always 4 bytes long,
+ so try for that first, then try the 2 byte JALR/JR.
+ FIXME: 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/JALX, 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/JALX at [target-2], but it could also
+ be the second word of a previous JAL/JALX, record it,
+ and check back one more. */
+ jmpaddr = addr;
+ else
+ /* Looks like a JAL/JALX at [target-3], so any previously
+ recorded JAL/JALX or JR/JALR must be wrong, because:
+
+ >-3: JAL
+ -2: JAL-ext (can't be JAL/JALX)
+ -1: bdslot (can't be JR/JALR)
+ 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: JR/JALR
+ 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/JALX, 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:
@@ -6230,6 +6460,8 @@ mips_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
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);