From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 991 invoked by alias); 28 Jul 2009 16:37:00 -0000 Received: (qmail 981 invoked by uid 22791); 28 Jul 2009 16:36:58 -0000 X-SWARE-Spam-Status: No, hits=-1.0 required=5.0 tests=AWL,BAYES_00,TBC X-Spam-Check-By: sourceware.org Received: from NaN.false.org (HELO nan.false.org) (208.75.86.248) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Tue, 28 Jul 2009 16:36:49 +0000 Received: from nan.false.org (localhost [127.0.0.1]) by nan.false.org (Postfix) with ESMTP id 4281B109FD for ; Tue, 28 Jul 2009 16:36:47 +0000 (GMT) Received: from caradoc.them.org (209.195.188.212.nauticom.net [209.195.188.212]) by nan.false.org (Postfix) with ESMTP id 028F1105BB for ; Tue, 28 Jul 2009 16:36:47 +0000 (GMT) Received: from drow by caradoc.them.org with local (Exim 4.69) (envelope-from ) id 1MVpfR-00069c-Rb for gdb-patches@sourceware.org; Tue, 28 Jul 2009 12:36:45 -0400 Date: Tue, 28 Jul 2009 17:34:00 -0000 From: Daniel Jacobowitz To: gdb-patches@sourceware.org Subject: Support for single-stepping Thumb-2 programs Message-ID: <20090728163645.GA22855@caradoc.them.org> Mail-Followup-To: gdb-patches@sourceware.org MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline User-Agent: Mutt/1.5.20 (2009-06-14) X-IsSubscribed: yes 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: 2009-07/txt/msg00686.txt.bz2 This patch adds software single step decoding for Thumb-2. Mostly this is straightforward; there are a couple of new 16-bit instructions (CBNZ, CBZ), a host of new 32-bit instructions we have to match, plus the default for 32-bit instructions is to skip 32 bits instead of 16. There's also support for the IT instruction. IT is an alternate form of conditional execution; you write this: ITF EQ MOVEQ R0, R1 MOVNE R0, R2 The IT instruction sets up flags in CPSR, which cause the MOVEQ and MOVNE (which, in Thumb-2 mode, assemble to ordinary MOVs) to be executed if the conditions indicate. The T is either "Then" or "True", and the F is "False". It's not too complicated to figure out from this whether we're in an IT block. Unfortunately it's quite hard to single step at this point. The condition codes are allowed to change during the block, so an instruction can alter whether the next instruction executes. Also, a block might end with an indirect jump. It's difficult to figure out where we're going to end up after an IT instruction or any instruction inside an IT block. There's an instruction, BKPT, with the nice property of being executed unconditionally even inside an IT block. Unfortunately we can't use it for breakpoints on GNU/Linux; it would trap an attached hardware debugger. The undefined instruction we currently use does not have this property. Also, it's 16-bit; setting a 16-bit breakpoint on a 32-bit instruction inside an IT block is trouble, because if the breakpoint is skipped we stop in the middle of an instruction. So the patch does not allow single stepping inside an IT block. Not explicitly mentioned in the patch is that it also doesn't address setting breakpoints inside an IT block, which has the same problem. My best plan to date to handle stepping is to reuse displaced stepping for this somehow. Fill the rest of the IT block with breakpoints or nops to simulate just the instruction we're interested in. As for user set breakpoints, I think we'll have to adjust them to point at the IT instruction instead. I haven't implemented any of that yet. This is generally useful as-is, so I'm submitting it without those potential improvements. The patch also includes a followup to my previous patch, to handle Thumb entry points; we must mark the return address as Thumb, or else two things go wrong. We'll try to interpret a Thumb breakpoint instruction as half of an ARM instruction and we'll fault on M-profile cores which do not implement ARM mode. This has been in our tree for a while and thoroughly tested. I retested on ARM GNU/Linux and will check it in. -- Daniel Jacobowitz CodeSourcery 2009-07-28 Daniel Jacobowitz * arm-tdep.c (arm_push_dummy_call): Set the low bit of LR for a Thumb entry point. (thumb_get_next_pc): Handle Thumb-2 and ARM v6 instructions. Refuse to single step into IT blocks. --- gdb/arm-tdep.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 204 insertions(+), 8 deletions(-) Index: gdb-mainline/gdb/arm-tdep.c =================================================================== --- gdb-mainline.orig/gdb/arm-tdep.c 2009-07-23 11:59:57.000000000 -0700 +++ gdb-mainline/gdb/arm-tdep.c 2009-07-23 12:51:57.000000000 -0700 @@ -1391,7 +1391,8 @@ arm_push_dummy_call (struct gdbarch *gdb /* Set the return address. For the ARM, the return breakpoint is always at BP_ADDR. */ - /* XXX Fix for Thumb. */ + if (arm_pc_is_thumb (bp_addr)) + bp_addr |= 1; regcache_cooked_write_unsigned (regcache, ARM_LR_REGNUM, bp_addr); /* Walk through the list of args and determine how large a temporary @@ -1835,9 +1836,43 @@ thumb_get_next_pc (struct frame_info *fr unsigned short inst1; CORE_ADDR nextpc = pc + 2; /* default is next instruction */ unsigned long offset; + ULONGEST status, it; inst1 = read_memory_unsigned_integer (pc, 2, byte_order_for_code); + /* Thumb-2 conditional execution support. There are eight bits in + the CPSR which describe conditional execution state. Once + reconstructed (they're in a funny order), the low five bits + describe the low bit of the condition for each instruction and + how many instructions remain. The high three bits describe the + base condition. One of the low four bits will be set if an IT + block is active. These bits read as zero on earlier + processors. */ + status = get_frame_register_unsigned (frame, ARM_PS_REGNUM); + it = ((status >> 8) & 0xfc) | ((status >> 25) & 0x3); + + /* On GNU/Linux, where this routine is used, we use an undefined + instruction as a breakpoint. Unlike BKPT, IT can disable execution + of the undefined instruction. So we might miss the breakpoint! */ + if ((inst1 & 0xff00) == 0xbf00 || (it & 0x0f)) + error (_("Stepping through Thumb-2 IT blocks is not yet supported")); + + if (it & 0x0f) + { + /* We are in a conditional block. Check the condition. */ + int cond = it >> 4; + + if (! condition_true (cond, status)) + { + /* Advance to the next instruction. All the 32-bit + instructions share a common prefix. */ + if ((inst1 & 0xe000) == 0xe000 && (inst1 & 0x1800) != 0) + return pc + 4; + else + return pc + 2; + } + } + if ((inst1 & 0xff00) == 0xbd00) /* pop {rlist, pc} */ { CORE_ADDR sp; @@ -1853,7 +1888,6 @@ thumb_get_next_pc (struct frame_info *fr } else if ((inst1 & 0xf000) == 0xd000) /* conditional branch */ { - unsigned long status = get_frame_register_unsigned (frame, ARM_PS_REGNUM); unsigned long cond = bits (inst1, 8, 11); if (cond != 0x0f && condition_true (cond, status)) /* 0x0f = SWI */ nextpc = pc_val + (sbits (inst1, 0, 7) << 1); @@ -1862,15 +1896,166 @@ thumb_get_next_pc (struct frame_info *fr { nextpc = pc_val + (sbits (inst1, 0, 10) << 1); } - else if ((inst1 & 0xf800) == 0xf000) /* long branch with link, and blx */ + else if ((inst1 & 0xe000) == 0xe000) /* 32-bit instruction */ { unsigned short inst2; inst2 = read_memory_unsigned_integer (pc + 2, 2, byte_order_for_code); - offset = (sbits (inst1, 0, 10) << 12) + (bits (inst2, 0, 10) << 1); - nextpc = pc_val + offset; - /* For BLX make sure to clear the low bits. */ - if (bits (inst2, 11, 12) == 1) - nextpc = nextpc & 0xfffffffc; + + /* Default to the next instruction. */ + nextpc = pc + 4; + + if ((inst1 & 0xf800) == 0xf000 && (inst2 & 0x8000) == 0x8000) + { + /* Branches and miscellaneous control instructions. */ + + if ((inst2 & 0x1000) != 0 || (inst2 & 0xd001) == 0xc000) + { + /* B, BL, BLX. */ + int j1, j2, imm1, imm2; + + imm1 = sbits (inst1, 0, 10); + imm2 = bits (inst2, 0, 10); + j1 = bit (inst2, 13); + j2 = bit (inst2, 11); + + offset = ((imm1 << 12) + (imm2 << 1)); + offset ^= ((!j2) << 22) | ((!j1) << 23); + + nextpc = pc_val + offset; + /* For BLX make sure to clear the low bits. */ + if (bit (inst2, 12) == 0) + nextpc = nextpc & 0xfffffffc; + } + else if (inst1 == 0xf3de && (inst2 & 0xff00) == 0x3f00) + { + /* SUBS PC, LR, #imm8. */ + nextpc = get_frame_register_unsigned (frame, ARM_LR_REGNUM); + nextpc -= inst2 & 0x00ff; + } + else if ((inst2 & 0xd000) == 0xc000 && (inst1 & 0x0380) != 0x0380) + { + /* Conditional branch. */ + if (condition_true (bits (inst1, 6, 9), status)) + { + int sign, j1, j2, imm1, imm2; + + sign = sbits (inst1, 10, 10); + imm1 = bits (inst1, 0, 5); + imm2 = bits (inst2, 0, 10); + j1 = bit (inst2, 13); + j2 = bit (inst2, 11); + + offset = (sign << 20) + (j2 << 19) + (j1 << 18); + offset += (imm1 << 12) + (imm2 << 1); + + nextpc = pc_val + offset; + } + } + } + else if ((inst1 & 0xfe50) == 0xe810) + { + /* Load multiple or RFE. */ + int rn, offset, load_pc = 1; + + rn = bits (inst1, 0, 3); + if (bit (inst1, 7) && !bit (inst1, 8)) + { + /* LDMIA or POP */ + if (!bit (inst2, 15)) + load_pc = 0; + offset = bitcount (inst2) * 4 - 4; + } + else if (!bit (inst1, 7) && bit (inst1, 8)) + { + /* LDMDB */ + if (!bit (inst2, 15)) + load_pc = 0; + offset = -4; + } + else if (bit (inst1, 7) && bit (inst1, 8)) + { + /* RFEIA */ + offset = 0; + } + else if (!bit (inst1, 7) && !bit (inst1, 8)) + { + /* RFEDB */ + offset = -8; + } + else + load_pc = 0; + + if (load_pc) + { + CORE_ADDR addr = get_frame_register_unsigned (frame, rn); + nextpc = get_frame_memory_unsigned (frame, addr + offset, 4); + } + } + else if ((inst1 & 0xffef) == 0xea4f && (inst2 & 0xfff0) == 0x0f00) + { + /* MOV PC or MOVS PC. */ + nextpc = get_frame_register_unsigned (frame, bits (inst2, 0, 3)); + } + else if ((inst1 & 0xff70) == 0xf850 && (inst2 & 0xf000) == 0xf000) + { + /* LDR PC. */ + CORE_ADDR base; + int rn, load_pc = 1; + + rn = bits (inst1, 0, 3); + base = get_frame_register_unsigned (frame, rn); + if (rn == 15) + { + base = (base + 4) & ~(CORE_ADDR) 0x3; + if (bit (inst1, 7)) + base += bits (inst2, 0, 11); + else + base -= bits (inst2, 0, 11); + } + else if (bit (inst1, 7)) + base += bits (inst2, 0, 11); + else if (bit (inst2, 11)) + { + if (bit (inst2, 10)) + { + if (bit (inst2, 9)) + base += bits (inst2, 0, 7); + else + base -= bits (inst2, 0, 7); + } + } + else if ((inst2 & 0x0fc0) == 0x0000) + { + int shift = bits (inst2, 4, 5), rm = bits (inst2, 0, 3); + base += get_frame_register_unsigned (frame, rm) << shift; + } + else + /* Reserved. */ + load_pc = 0; + + if (load_pc) + nextpc = get_frame_memory_unsigned (frame, base, 4); + } + else if ((inst1 & 0xfff0) == 0xe8d0 && (inst2 & 0xfff0) == 0xf000) + { + /* TBB. */ + CORE_ADDR table, offset, length; + + table = get_frame_register_unsigned (frame, bits (inst1, 0, 3)); + offset = get_frame_register_unsigned (frame, bits (inst2, 0, 3)); + length = 2 * get_frame_memory_unsigned (frame, table + offset, 1); + nextpc = pc_val + length; + } + else if ((inst1 & 0xfff0) == 0xe8d0 && (inst2 & 0xfff0) == 0xf000) + { + /* TBH. */ + CORE_ADDR table, offset, length; + + table = get_frame_register_unsigned (frame, bits (inst1, 0, 3)); + offset = 2 * get_frame_register_unsigned (frame, bits (inst2, 0, 3)); + length = 2 * get_frame_memory_unsigned (frame, table + offset, 2); + nextpc = pc_val + length; + } } else if ((inst1 & 0xff00) == 0x4700) /* bx REG, blx REG */ { @@ -1883,6 +2068,17 @@ thumb_get_next_pc (struct frame_info *fr if (nextpc == pc) error (_("Infinite loop detected")); } + else if ((inst1 & 0xf500) == 0xb100) + { + /* CBNZ or CBZ. */ + int imm = (bit (inst1, 9) << 6) + (bits (inst1, 3, 7) << 1); + ULONGEST reg = get_frame_register_unsigned (frame, bits (inst1, 0, 2)); + + if (bit (inst1, 11) && reg != 0) + nextpc = pc_val + imm; + else if (!bit (inst1, 11) && reg == 0) + nextpc = pc_val + imm; + } return nextpc; }