(arm_linux_thumb2_le_breakpoint): New constants.
(arm_linux_init_abi): Set thumb2_breakpoint and
thumb2_breakpoint_size.
* arm-tdep.c (thumb_insn_size, thumb_advance_itstate): New functions.
(thumb_get_next_pc): Add a comment. Rename IT to ITSTATE.
Implement support for single stepping through IT blocks if
a 32-bit Thumb breakpoint instruction is available.
(arm_breakpoint_from_pc): If a 32-bit Thumb breakpoint instruction
is available, use it when needed.
(arm_remote_breakpoint_from_pc): New function.
(arm_gdbarch_init): Register arm_remote_breakpoint_from_pc.
* arm-tdep.h (struct gdbarch_tdep): Correct thumb_breakpoint
comment. Add thumb2_breakpoint and thumb2_breakpoint_size.
gdbserver/
* linux-arm-low.c (thumb_breakpoint_len): Delete.
(thumb2_breakpoint): New.
(arm_breakpoint_at): Check for Thumb-2 breakpoints.
testsuite/
* gdb.arch/thumb2-it.S, gdb.arch/thumb2-it.exp: New files.
+2010-02-01 Daniel Jacobowitz <dan@codesourcery.com>
+
+ * arm-linux-tdep.c (arm_linux_thumb2_be_breakpoint)
+ (arm_linux_thumb2_le_breakpoint): New constants.
+ (arm_linux_init_abi): Set thumb2_breakpoint and
+ thumb2_breakpoint_size.
+ * arm-tdep.c (thumb_insn_size, thumb_advance_itstate): New functions.
+ (thumb_get_next_pc): Add a comment. Rename IT to ITSTATE.
+ Implement support for single stepping through IT blocks if
+ a 32-bit Thumb breakpoint instruction is available.
+ (arm_breakpoint_from_pc): If a 32-bit Thumb breakpoint instruction
+ is available, use it when needed.
+ (arm_remote_breakpoint_from_pc): New function.
+ (arm_gdbarch_init): Register arm_remote_breakpoint_from_pc.
+ * arm-tdep.h (struct gdbarch_tdep): Correct thumb_breakpoint
+ comment. Add thumb2_breakpoint and thumb2_breakpoint_size.
+
2010-02-01 Daniel Jacobowitz <dan@codesourcery.com>
* arch-utils.c (default_remote_breakpoint_from_pc): New function.
static const char arm_linux_thumb_le_breakpoint[] = {0x01, 0xde};
+/* Because the 16-bit Thumb breakpoint is affected by Thumb-2 IT blocks,
+ we must use a length-appropriate breakpoint for 32-bit Thumb
+ instructions. See also thumb_get_next_pc. */
+
+static const char arm_linux_thumb2_be_breakpoint[] = { 0xf7, 0xf0, 0xa0, 0x00 };
+
+static const char arm_linux_thumb2_le_breakpoint[] = { 0xf0, 0xf7, 0x00, 0xa0 };
+
/* Description of the longjmp buffer. */
#define ARM_LINUX_JB_ELEMENT_SIZE INT_REGISTER_SIZE
#define ARM_LINUX_JB_PC 21
else
tdep->arm_breakpoint = arm_linux_arm_be_breakpoint;
tdep->thumb_breakpoint = arm_linux_thumb_be_breakpoint;
+ tdep->thumb2_breakpoint = arm_linux_thumb2_be_breakpoint;
}
else
{
else
tdep->arm_breakpoint = arm_linux_arm_le_breakpoint;
tdep->thumb_breakpoint = arm_linux_thumb_le_breakpoint;
+ tdep->thumb2_breakpoint = arm_linux_thumb2_le_breakpoint;
}
tdep->arm_breakpoint_size = sizeof (arm_linux_arm_le_breakpoint);
tdep->thumb_breakpoint_size = sizeof (arm_linux_thumb_le_breakpoint);
+ tdep->thumb2_breakpoint_size = sizeof (arm_linux_thumb2_le_breakpoint);
if (tdep->fp_model == ARM_FLOAT_AUTO)
tdep->fp_model = ARM_FLOAT_FPA;
return nbits;
}
+/* Return the size in bytes of the complete Thumb instruction whose
+ first halfword is INST1. */
+
+static int
+thumb_insn_size (unsigned short inst1)
+{
+ if ((inst1 & 0xe000) == 0xe000 && (inst1 & 0x1800) != 0)
+ return 4;
+ else
+ return 2;
+}
+
+static int
+thumb_advance_itstate (unsigned int itstate)
+{
+ /* Preserve IT[7:5], the first three bits of the condition. Shift
+ the upcoming condition flags left by one bit. */
+ itstate = (itstate & 0xe0) | ((itstate << 1) & 0x1f);
+
+ /* If we have finished the IT block, clear the state. */
+ if ((itstate & 0x0f) == 0)
+ itstate = 0;
+
+ return itstate;
+}
+
+/* Find the next PC after the current instruction executes. In some
+ cases we can not statically determine the answer (see the IT state
+ handling in this function); in that case, a breakpoint may be
+ inserted in addition to the returned PC, which will be used to set
+ another breakpoint by our caller. */
+
static CORE_ADDR
thumb_get_next_pc (struct frame_info *frame, CORE_ADDR pc)
{
struct gdbarch *gdbarch = get_frame_arch (frame);
+ struct address_space *aspace = get_frame_address_space (frame);
enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
unsigned long pc_val = ((unsigned long) pc) + 4; /* PC after prefetch */
unsigned short inst1;
CORE_ADDR nextpc = pc + 2; /* default is next instruction */
unsigned long offset;
- ULONGEST status, it;
+ ULONGEST status, itstate;
inst1 = read_memory_unsigned_integer (pc, 2, byte_order_for_code);
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);
+ itstate = ((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 && (inst1 & 0x000f) != 0) || (it & 0x0f))
- error (_("Stepping through Thumb-2 IT blocks is not yet supported"));
+ /* If-Then handling. 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 we set it on a skipped conditional
+ instruction. Because conditional instructions can change the
+ flags, affecting the execution of further instructions, we may
+ need to set two breakpoints. */
- if (it & 0x0f)
+ if (gdbarch_tdep (gdbarch)->thumb2_breakpoint != NULL)
+ {
+ if ((inst1 & 0xff00) == 0xbf00 && (inst1 & 0x000f) != 0)
+ {
+ /* An IT instruction. Because this instruction does not
+ modify the flags, we can accurately predict the next
+ executed instruction. */
+ itstate = inst1 & 0x00ff;
+ pc += thumb_insn_size (inst1);
+
+ while (itstate != 0 && ! condition_true (itstate >> 4, status))
+ {
+ inst1 = read_memory_unsigned_integer (pc, 2, byte_order_for_code);
+ pc += thumb_insn_size (inst1);
+ itstate = thumb_advance_itstate (itstate);
+ }
+
+ return pc;
+ }
+ else if (itstate != 0)
+ {
+ /* We are in a conditional block. Check the condition. */
+ if (! condition_true (itstate >> 4, status))
+ {
+ /* Advance to the next executed instruction. */
+ pc += thumb_insn_size (inst1);
+ itstate = thumb_advance_itstate (itstate);
+
+ while (itstate != 0 && ! condition_true (itstate >> 4, status))
+ {
+ inst1 = read_memory_unsigned_integer (pc, 2, byte_order_for_code);
+ pc += thumb_insn_size (inst1);
+ itstate = thumb_advance_itstate (itstate);
+ }
+
+ return pc;
+ }
+ else if ((itstate & 0x0f) == 0x08)
+ {
+ /* This is the last instruction of the conditional
+ block, and it is executed. We can handle it normally
+ because the following instruction is not conditional,
+ and we must handle it normally because it is
+ permitted to branch. Fall through. */
+ }
+ else
+ {
+ int cond_negated;
+
+ /* There are conditional instructions after this one.
+ If this instruction modifies the flags, then we can
+ not predict what the next executed instruction will
+ be. Fortunately, this instruction is architecturally
+ forbidden to branch; we know it will fall through.
+ Start by skipping past it. */
+ pc += thumb_insn_size (inst1);
+ itstate = thumb_advance_itstate (itstate);
+
+ /* Set a breakpoint on the following instruction. */
+ gdb_assert ((itstate & 0x0f) != 0);
+ insert_single_step_breakpoint (gdbarch, aspace, pc);
+ cond_negated = (itstate >> 4) & 1;
+
+ /* Skip all following instructions with the same
+ condition. If there is a later instruction in the IT
+ block with the opposite condition, set the other
+ breakpoint there. If not, then set a breakpoint on
+ the instruction after the IT block. */
+ do
+ {
+ inst1 = read_memory_unsigned_integer (pc, 2, byte_order_for_code);
+ pc += thumb_insn_size (inst1);
+ itstate = thumb_advance_itstate (itstate);
+ }
+ while (itstate != 0 && ((itstate >> 4) & 1) == cond_negated);
+
+ return pc;
+ }
+ }
+ }
+ else if (itstate & 0x0f)
{
/* We are in a conditional block. Check the condition. */
- int cond = it >> 4;
+ int cond = itstate >> 4;
if (! condition_true (cond, status))
{
else
return pc + 2;
}
+
+ /* Otherwise, handle the instruction normally. */
}
if ((inst1 & 0xff00) == 0xbd00) /* pop {rlist, pc} */
arm_breakpoint_from_pc (struct gdbarch *gdbarch, CORE_ADDR *pcptr, int *lenptr)
{
struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+ enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
if (arm_pc_is_thumb (*pcptr))
{
*pcptr = UNMAKE_THUMB_ADDR (*pcptr);
+
+ /* If we have a separate 32-bit breakpoint instruction for Thumb-2,
+ check whether we are replacing a 32-bit instruction. */
+ if (tdep->thumb2_breakpoint != NULL)
+ {
+ gdb_byte buf[2];
+ if (target_read_memory (*pcptr, buf, 2) == 0)
+ {
+ unsigned short inst1;
+ inst1 = extract_unsigned_integer (buf, 2, byte_order_for_code);
+ if ((inst1 & 0xe000) == 0xe000 && (inst1 & 0x1800) != 0)
+ {
+ *lenptr = tdep->thumb2_breakpoint_size;
+ return tdep->thumb2_breakpoint;
+ }
+ }
+ }
+
*lenptr = tdep->thumb_breakpoint_size;
return tdep->thumb_breakpoint;
}
}
}
+static void
+arm_remote_breakpoint_from_pc (struct gdbarch *gdbarch, CORE_ADDR *pcptr,
+ int *kindptr)
+{
+ struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+
+ arm_breakpoint_from_pc (gdbarch, pcptr, kindptr);
+
+ if (arm_pc_is_thumb (*pcptr) && *kindptr == 4)
+ /* The documented magic value for a 32-bit Thumb-2 breakpoint, so
+ that this is not confused with a 32-bit ARM breakpoint. */
+ *kindptr = 3;
+}
+
/* Extract from an array REGBUF containing the (raw) register state a
function return value of type TYPE, and copy that, in virtual
format, into VALBUF. */
/* Breakpoint manipulation. */
set_gdbarch_breakpoint_from_pc (gdbarch, arm_breakpoint_from_pc);
+ set_gdbarch_remote_breakpoint_from_pc (gdbarch,
+ arm_remote_breakpoint_from_pc);
/* Information about registers, etc. */
set_gdbarch_deprecated_fp_regnum (gdbarch, ARM_FP_REGNUM); /* ??? */
const char *arm_breakpoint; /* Breakpoint pattern for an ARM insn. */
int arm_breakpoint_size; /* And its size. */
- const char *thumb_breakpoint; /* Breakpoint pattern for an ARM insn. */
+ const char *thumb_breakpoint; /* Breakpoint pattern for a Thumb insn. */
int thumb_breakpoint_size; /* And its size. */
+ /* If the Thumb breakpoint is an undefined instruction (which is
+ affected by IT blocks) rather than a BKPT instruction (which is
+ not), then we need a 32-bit Thumb breakpoint to preserve the
+ instruction count in IT blocks. */
+ const char *thumb2_breakpoint;
+ int thumb2_breakpoint_size;
+
int jb_pc; /* Offset to PC value in jump buffer.
If this is negative, longjmp support
will be disabled. */
+2010-02-01 Daniel Jacobowitz <dan@codesourcery.com>
+
+ * linux-arm-low.c (thumb_breakpoint_len): Delete.
+ (thumb2_breakpoint): New.
+ (arm_breakpoint_at): Check for Thumb-2 breakpoints.
+
2010-01-29 Daniel Jacobowitz <dan@codesourcery.com>
* linux-low.c (get_stop_pc): Check for SIGTRAP.
static const unsigned long arm_breakpoint = 0xef9f0001;
#define arm_breakpoint_len 4
static const unsigned short thumb_breakpoint = 0xde01;
-#define thumb_breakpoint_len 2
+static const unsigned short thumb2_breakpoint[] = { 0xf7f0, 0xa000 };
/* For new EABI binaries. We recognize it regardless of which ABI
is used for gdbserver, so single threaded debugging should work
(*the_target->read_memory) (where, (unsigned char *) &insn, 2);
if (insn == thumb_breakpoint)
return 1;
+
+ if (insn == thumb2_breakpoint[0])
+ {
+ (*the_target->read_memory) (where + 2, (unsigned char *) &insn, 2);
+ if (insn == thumb2_breakpoint[1])
+ return 1;
+ }
}
else
{
+2010-02-01 Daniel Jacobowitz <dan@codesourcery.com>
+
+ * gdb.arch/thumb2-it.S, gdb.arch/thumb2-it.exp: New files.
+
2010-01-29 Daniel Jacobowitz <dan@codesourcery.com>
* gdb.base/call-strs.exp, gdb.base/default.exp,
--- /dev/null
+/* Thumb-2 IT blocks test program.
+
+ Copyright 2010 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+ .syntax unified
+ .text
+ .p2align 2
+ .code 16
+
+#ifndef __thumb2__
+
+ .type main,%function
+ .thumb_func
+ .globl main
+main:
+ mov r0, #0
+ bx lr @ No Thumb-2
+
+#else
+
+ .type main,%function
+ .thumb_func
+ .globl main
+main:
+ mov r0, #0
+ bx lr @ Thumb-2 OK
+
+ @ One conditional instruction, executed.
+ .type it_1,%function
+ .thumb_func
+it_1:
+ mov r0, #0 @ Setup
+ cmp r0, #0 @ Setup
+ it eq @ IT instruction, Expected == 1
+ addeq r0, #1 @ Reached
+ bx lr @ Done
+
+ @ One conditional instruction, skipped.
+ .type it_2,%function
+ .thumb_func
+it_2:
+ mov r0, #0 @ Setup
+ cmp r0, #0 @ Setup
+ it ne @ IT instruction, Expected == 0
+ addne r0, #1 @ Not reached
+ bx lr @ Done, Check $r0 == 0
+
+ @ Block of four, alternating, starting with executed.
+ .type it_3,%function
+ .thumb_func
+it_3:
+ mov r0, #0 @ Setup
+ cmp r0, #0 @ Setup
+ itete ge @ IT instruction, Expected == 2
+ addge r0, #1 @ Reached
+ addlt r0, #2 @ Not reached
+ addge r0, #4 @ Reached
+ addlt r0, #8 @ Not reached
+ bx lr @ Done, Check $r0 == 5
+
+ @ Block of four, changing flags.
+ .type it_4,%function
+ .thumb_func
+it_4:
+ mov r0, #0 @ Setup
+ cmp r0, #0 @ Setup
+ itttt ge @ IT instruction, Expected == 2
+ addge r0, #1 @ Reached
+ cmpge r0, #10 @ Reached
+ addge r0, #4 @ Not reached
+ addge r0, #8 @ Not reached
+ bx lr @ Done, Check $r0 == 1
+
+ @ Block of two, ending with taken branch.
+ .type it_5,%function
+ .thumb_func
+it_5:
+ mov r0, #0 @ Setup
+ cmp r0, #0 @ Setup
+ itt ge @ IT instruction, Expected == 2
+ addge r0, #1 @ Reached
+ bge .L5 @ Reached
+ add r0, #2 @ Never reached
+.L5: bx lr @ Done, Check $r0 == 1
+
+ @ Block of two, ending with untaken branch.
+ .type it_6,%function
+ .thumb_func
+it_6:
+ mov r0, #0 @ Setup
+ cmp r0, #0 @ Setup
+ ite ge @ IT instruction, Expected == 2
+ addge r0, #1 @ Reached
+ blt .L6 @ Not reached
+ add r0, #2 @ Reached
+.L6: bx lr @ Done, Check $r0 == 3
+
+ @ Block of four, taken, of different sizes
+ .type it_7,%function
+ .thumb_func
+it_7:
+ mov r0, #0 @ Setup
+ cmp r0, #0 @ Setup
+ itttt ge @ IT instruction, Expected == 4
+ addge.n r0, #1 @ Reached
+ addge.w r0, #2 @ Reached
+ addge.n r0, #4 @ Reached
+ addge.w r0, #8 @ Reached
+ bx lr @ Done, Check $r0 == 15
+
+ @ Block of four, only first executed.
+ .type it_3,%function
+ .thumb_func
+it_8:
+ mov r0, #0 @ Setup
+ cmp r0, #0 @ Setup
+ iteee ge @ IT instruction, Expected == 1
+ addge r0, #1 @ Reached
+ addlt r0, #2 @ Not reached
+ addlt r0, #4 @ Not reached
+ addlt r0, #8 @ Not reached
+ bx lr @ Done, Check $r0 == 1
+
+#endif /* __thumb2__ */
--- /dev/null
+# Copyright 2010 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test single stepping over Thumb-2 IT blocks.
+
+if {![istarget arm*-*eabi*]} then {
+ verbose "Skipping Thumb-2 tests."
+ return
+}
+
+set testfile "thumb2-it"
+set srcfile ${testfile}.S
+set binfile ${objdir}/${subdir}/${testfile}
+
+if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable debug] != "" } {
+ untested thumb2-it.exp
+ return -1
+}
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+if ![runto_main] then {
+ untested thumb2-it.exp
+ return -1
+}
+
+# Make sure that the compiler options allow Thumb-2.
+gdb_test_multiple "list" "list main" {
+ -re ".*@ No Thumb-2.*$gdb_prompt $" {
+ pass "list main"
+ untested thumb2-it.exp
+ return -1
+ }
+ -re ".*@ Thumb-2 OK.*$gdb_prompt $" {
+ pass "list main"
+ }
+}
+
+proc test_it_block { func } {
+ global gdb_prompt
+ global software_step
+
+ if { ! [gdb_breakpoint "*${func}"] } {
+ unresolved "$func, IT block tests"
+ return
+ }
+
+ gdb_test "call ${func}()" "Breakpoint.*@ Setup.*" "$func, call"
+
+ set expected 0
+ set reached 0
+ set steps 0
+ set ok 1
+ while { $ok } {
+ set ok 0
+ set msg "$func, stepi $steps"
+ gdb_test_multiple "stepi" "$msg" {
+ -re ".*@ Setup.*$gdb_prompt $" {
+ pass "$msg"
+ set ok 1
+ }
+ -re ".*@ IT instruction, Expected == (\[0-9\]*)\r\n$gdb_prompt $" {
+ set expected $expect_out(1,string)
+ pass "$msg"
+ set ok 1
+ }
+ -re ".*@ Reached.*$gdb_prompt $" {
+ incr reached
+ pass "$msg"
+ set ok 1
+ if { [regexp {@ Reached, Set ([^\r\n]*)\r\n} $expect_out(0,string) dummy change] } {
+ gdb_test "set $change" "" "$func, set $change"
+ }
+ }
+ -re ".*@ Not reached.*$gdb_prompt $" {
+ # An instruction in an IT block whose predicate is false when
+ # we reach it. If using software single step, we should not
+ # stop here.
+ if { $software_step } {
+ fail "$msg"
+ } else {
+ pass "$msg"
+ set ok 1
+ }
+ }
+ -re ".*@ Never reached.*$gdb_prompt $" {
+ # An instruction that should be branched over.
+ fail "$msg"
+ }
+ -re ".*@ Done.*$gdb_prompt $" {
+ pass "$msg"
+ if { $reached == $expected } {
+ pass "$func, correct instructions reached"
+ } else {
+ fail "$func, correct instructions reached"
+ }
+ if { [regexp {@ Done, Check ([^\r\n]*)\r\n} $expect_out(0,string) dummy check] } {
+ gdb_test "print $check" ".* = 1" "$func, $check"
+ }
+ }
+ }
+ if { ! $ok } {
+ break
+ }
+ incr steps
+ continue
+ }
+
+ gdb_test "continue" "" "$func, continue"
+ return
+}
+
+# If we are using software single-stepping in GDB, then GDB will not
+# stop at conditional instructions with a false predicate during stepi.
+# If we are using a simulator or debug interface with hardware single
+# step, then GDB will stop at such instructions.
+if { [istarget arm*-linux*] } {
+ set software_step 1
+} else {
+ set software_step 0
+}
+
+for { set i 1 } { $i <= 8 } { incr i } {
+ test_it_block it_${i}
+}