From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 24527 invoked by alias); 24 May 2007 18:08:02 -0000 Received: (qmail 24052 invoked by uid 22791); 24 May 2007 18:07:58 -0000 X-Spam-Check-By: sourceware.org Received: from dmz.mips-uk.com (HELO dmz.mips-uk.com) (194.74.144.194) by sourceware.org (qpsmtpd/0.31) with ESMTP; Thu, 24 May 2007 18:07:55 +0000 Received: from internal-mx1 ([192.168.192.240] helo=ukservices1.mips.com) by dmz.mips-uk.com with esmtp (Exim 3.35 #1 (Debian)) id 1HrHj6-0000IR-00; Thu, 24 May 2007 19:07:52 +0100 Received: from perivale.mips.com ([192.168.192.200]) by ukservices1.mips.com with esmtp (Exim 3.36 #1 (Debian)) id 1HrHj0-0008PY-00; Thu, 24 May 2007 19:07:46 +0100 Received: from macro (helo=localhost) by perivale.mips.com with local-esmtp (Exim 4.63) (envelope-from ) id 1HrHj0-00027q-SY; Thu, 24 May 2007 19:07:46 +0100 Date: Thu, 24 May 2007 18:08:00 -0000 From: "Maciej W. Rozycki" To: gdb-patches@sourceware.org cc: Chris Dearman , "Maciej W. Rozycki" Subject: mips-tdep.c: Adjust breakpoints in branch delay slots Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII X-MIPS-Technologies-UK-MailScanner: Found to be clean X-MIPS-Technologies-UK-MailScanner-From: macro@mips.com Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2007-05/txt/msg00375.txt.bz2 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 : jr ra 0x400670 : 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 Maciej W. Rozycki * 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);