* 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.

	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.
This commit is contained in:
Daniel Jacobowitz 2010-02-01 16:13:17 +00:00
parent a1dcb23a28
commit 177321bd85
9 changed files with 494 additions and 11 deletions

View File

@ -1,3 +1,20 @@
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.

View File

@ -74,6 +74,14 @@ static const char arm_linux_thumb_be_breakpoint[] = {0xde, 0x01};
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
@ -851,6 +859,7 @@ arm_linux_init_abi (struct gdbarch_info info,
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
{
@ -859,9 +868,11 @@ arm_linux_init_abi (struct gdbarch_info info,
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;

View File

@ -2256,17 +2256,50 @@ bitcount (unsigned long val)
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);
@ -2279,18 +2312,100 @@ thumb_get_next_pc (struct frame_info *frame, CORE_ADDR pc)
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))
{
@ -2301,6 +2416,8 @@ thumb_get_next_pc (struct frame_info *frame, CORE_ADDR pc)
else
return pc + 2;
}
/* Otherwise, handle the instruction normally. */
}
if ((inst1 & 0xff00) == 0xbd00) /* pop {rlist, pc} */
@ -4749,10 +4866,29 @@ static const unsigned char *
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;
}
@ -4763,6 +4899,20 @@ arm_breakpoint_from_pc (struct gdbarch *gdbarch, CORE_ADDR *pcptr, int *lenptr)
}
}
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. */
@ -6091,6 +6241,8 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
/* 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); /* ??? */

View File

@ -167,9 +167,16 @@ struct gdbarch_tdep
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. */

View File

@ -1,3 +1,9 @@
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.

View File

@ -203,7 +203,7 @@ arm_set_pc (struct regcache *regcache, CORE_ADDR pc)
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
@ -227,6 +227,13 @@ arm_breakpoint_at (CORE_ADDR where)
(*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
{

View File

@ -1,3 +1,7 @@
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,

View File

@ -0,0 +1,139 @@
/* 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__ */

View File

@ -0,0 +1,140 @@
# 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}
}