binutils-gdb/gdb/mem-break.c
Maciej W. Rozycki 0d5ed15352 Avoid software breakpoint's instruction shadow inconsistency
This change:

commit b775012e84
Author: Luis Machado <luisgpm@br.ibm.com>
Date:   Fri Feb 24 15:10:59 2012 +0000

    2012-02-24  Luis Machado  <lgustavo@codesourcery.com>

	* remote.c (remote_supports_cond_breakpoints): New forward
	declaration.
[...]

changed the way breakpoints are inserted and removed such that
`insert_bp_location' can now be called with the breakpoint being handled
already in place, while previously the call was only ever made for
breakpoints that have not been put in place.  This in turn caused an
issue for software breakpoints and targets for which a breakpoint's
`placed_address' may not be the same as the original requested address.

The issue is `insert_bp_location' overwrites the previously adjusted
value in `placed_address' with the original address, that is only
replaced back with the correct adjusted address later on when
`gdbarch_breakpoint_from_pc' is called.  Meanwhile there's a window
where the value in `placed_address' does not correspond to data stored
in `shadow_contents', leading to incorrect instruction bytes being
supplied when `one_breakpoint_xfer_memory' is called to supply the
instruction overlaid by the breakpoint.

And this is exactly what happens on the MIPS target with software
breakpoints placed in microMIPS code.  In this case not only
`placed_address' is not the original address because of the ISA bit, but
`mips_breakpoint_from_pc' has to read the original instruction to
determine which one of the two software breakpoint instruction encodings
to choose as well.  The 16-bit encoding is used to replace 16-bit
instructions and similarly the 32-bit one is used with 32-bit
instructions, to satisfy branch delay slot size requirements.

The mismatch between `placed_address' and the address data in
`shadow_contents' has been obtained from leads to the wrong encoding
being used in some cases, which in the case of a 32-bit software
breakpoint instruction replacing a 16-bit instruction causes corruption
to the adjacent following instruction and leads the debug session astray
if execution reaches there e.g. with a jump.

To address this problem I made the change below, that adds a
`reqstd_address' field to `struct bp_target_info' and leaves
`placed_address' unchanged once it has been set.  This ensures data in
`shadow_contents' is always consistent with `placed_address'.

This approach also has this good side effect that all the places that
examine the breakpoint's address see a consistent value, either
`reqstd_address' or `placed_address', as required.  Currently some
places see either the original or the adjusted address in
`placed_address', depending on whether they have been called before
`gdbarch_remote_breakpoint_from_pc' or afterwards.  This is in
particular true for subsequent calls to
`gdbarch_remote_breakpoint_from_pc' itself, e.g. from
`one_breakpoint_xfer_memory'.  This is also important for places like
`find_single_step_breakpoint' where a breakpoint's address is compared
to the raw value of $pc.

	* breakpoint.h (bp_target_info): Add `reqstd_address' member,
	update comments.
	* breakpoint.c (one_breakpoint_xfer_memory): Use `reqstd_address'
	for the breakpoint's address.  Don't preinitialize `placed_size'.
	(insert_bp_location): Set `reqstd_address' rather than
	`placed_address'.
	(bp_target_info_copy_insertion_state): Also copy `placed_address'.
	(bkpt_insert_location): Use `reqstd_address' for the breakpoint's
	address.
	(bkpt_remove_location): Likewise.
	(deprecated_insert_raw_breakpoint): Likewise.
	(deprecated_remove_raw_breakpoint): Likewise.
	(find_single_step_breakpoint): Likewise.
	* mem-break.c (default_memory_insert_breakpoint): Use
	`reqstd_address' for the breakpoint's address.  Don't set
	`placed_address' or `placed_size' if breakpoint contents couldn't
	have been determined.
	* remote.c (remote_insert_breakpoint): Use `reqstd_address' for
	the breakpoint's address.
	(remote_insert_hw_breakpoint): Likewise.  Don't set
	`placed_address' or `placed_size' if breakpoint couldn't have been
	set.
	* aarch64-linux-nat.c (aarch64_linux_insert_hw_breakpoint): Use
	`reqstd_address' for the breakpoint's address.
	* arm-linux-nat.c (arm_linux_hw_breakpoint_initialize): Likewise.
	* ia64-tdep.c (ia64_memory_insert_breakpoint): Likewise.
	* m32r-tdep.c (m32r_memory_insert_breakpoint): Likewise.
	* microblaze-linux-tdep.c
	(microblaze_linux_memory_remove_breakpoint): Likewise.
	* monitor.c (monitor_insert_breakpoint): Likewise.
	* nto-procfs.c (procfs_insert_breakpoint): Likewise.
	(procfs_insert_hw_breakpoint): Likewise.
	* ppc-linux-nat.c (ppc_linux_insert_hw_breakpoint): Likewise.
	* ppc-linux-tdep.c (ppc_linux_memory_remove_breakpoint): Likewise.
	* remote-m32r-sdi.c (m32r_insert_breakpoint): Likewise.
	* remote-mips.c (mips_insert_breakpoint): Likewise.
	* x86-nat.c (x86_insert_hw_breakpoint): Likewise.
2014-10-03 12:54:34 +01:00

123 lines
3.7 KiB
C

/* Simulate breakpoints by patching locations in the target system, for GDB.
Copyright (C) 1990-2014 Free Software Foundation, Inc.
Contributed by Cygnus Support. Written by John Gilmore.
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/>. */
#include "defs.h"
#include "symtab.h"
#include "breakpoint.h"
#include "inferior.h"
#include "target.h"
/* Insert a breakpoint on targets that don't have any better
breakpoint support. We read the contents of the target location
and stash it, then overwrite it with a breakpoint instruction.
BP_TGT->placed_address is the target location in the target
machine. BP_TGT->shadow_contents is some memory allocated for
saving the target contents. It is guaranteed by the caller to be
long enough to save BREAKPOINT_LEN bytes (this is accomplished via
BREAKPOINT_MAX). */
int
default_memory_insert_breakpoint (struct gdbarch *gdbarch,
struct bp_target_info *bp_tgt)
{
CORE_ADDR addr = bp_tgt->reqstd_address;
const unsigned char *bp;
gdb_byte *readbuf;
int bplen;
int val;
/* Determine appropriate breakpoint contents and size for this address. */
bp = gdbarch_breakpoint_from_pc (gdbarch, &addr, &bplen);
if (bp == NULL)
error (_("Software breakpoints not implemented for this target."));
bp_tgt->placed_address = addr;
bp_tgt->placed_size = bplen;
/* Save the memory contents in the shadow_contents buffer and then
write the breakpoint instruction. */
bp_tgt->shadow_len = bplen;
readbuf = alloca (bplen);
val = target_read_memory (addr, readbuf, bplen);
if (val == 0)
{
memcpy (bp_tgt->shadow_contents, readbuf, bplen);
val = target_write_raw_memory (addr, bp, bplen);
}
return val;
}
int
default_memory_remove_breakpoint (struct gdbarch *gdbarch,
struct bp_target_info *bp_tgt)
{
return target_write_raw_memory (bp_tgt->placed_address, bp_tgt->shadow_contents,
bp_tgt->placed_size);
}
int
memory_insert_breakpoint (struct target_ops *ops, struct gdbarch *gdbarch,
struct bp_target_info *bp_tgt)
{
return gdbarch_memory_insert_breakpoint (gdbarch, bp_tgt);
}
int
memory_remove_breakpoint (struct target_ops *ops, struct gdbarch *gdbarch,
struct bp_target_info *bp_tgt)
{
return gdbarch_memory_remove_breakpoint (gdbarch, bp_tgt);
}
int
memory_validate_breakpoint (struct gdbarch *gdbarch,
struct bp_target_info *bp_tgt)
{
CORE_ADDR addr = bp_tgt->placed_address;
const gdb_byte *bp;
int val;
int bplen;
gdb_byte cur_contents[BREAKPOINT_MAX];
struct cleanup *cleanup;
int ret;
/* Determine appropriate breakpoint contents and size for this
address. */
bp = gdbarch_breakpoint_from_pc (gdbarch, &addr, &bplen);
if (bp == NULL || bp_tgt->placed_size != bplen)
return 0;
/* Make sure we see the memory breakpoints. */
cleanup = make_show_memory_breakpoints_cleanup (1);
val = target_read_memory (addr, cur_contents, bplen);
/* If our breakpoint is no longer at the address, this means that
the program modified the code on us, so it is wrong to put back
the old value. */
ret = (val == 0 && memcmp (bp, cur_contents, bplen) == 0);
do_cleanups (cleanup);
return ret;
}