Linux native: Use TRAP_BRKPT/TRAP_HWBPT

This patch adjusts the native Linux target backend to tell the core
whether a trap was caused by a breakpoint.

It teaches the target to get that information out of the si_code of
the SIGTRAP siginfo.

Tested on x86-64 Fedora 20, s390 RHEL 7, and PPC64 Fedora 18.  An
earlier version was tested on ARM Fedora 21.

gdb/ChangeLog:
2015-03-04  Pedro Alves  <palves@redhat.com>

	* linux-nat.c (save_sigtrap): Check for breakpoints before
	checking watchpoints.
	(status_callback) [USE_SIGTRAP_SIGINFO]: Don't check whether a
	breakpoint is inserted if relying on SIGTRAP's siginfo.si_code.
	(check_stopped_by_breakpoint) [USE_SIGTRAP_SIGINFO]: Decide whether
	a breakpoint triggered based on the SIGTRAP's siginfo.si_code.
	(linux_nat_stopped_by_sw_breakpoint)
	(linux_nat_supports_stopped_by_sw_breakpoint)
	(linux_nat_stopped_by_hw_breakpoint)
	(linux_nat_supports_stopped_by_hw_breakpoint): New functions.
	(linux_nat_wait_1): Don't re-increment the PC if relying on
	SIGTRAP's siginfo->si_code.
	(linux_nat_add_target): Install new target methods.
	* linux-thread-db.c (check_event): Don't account for breakpoint PC
	offset if the target already adjusted the PC.
	* nat/linux-ptrace.h (USE_SIGTRAP_SIGINFO): New.
	(GDB_ARCH_TRAP_BRKPT): New.
	(TRAP_HWBKPT): Define if not already defined.
This commit is contained in:
Pedro Alves 2015-03-04 20:41:16 +00:00
parent f7e6eed528
commit faf09f0119
4 changed files with 180 additions and 7 deletions

View File

@ -1,3 +1,24 @@
2015-03-04 Pedro Alves <palves@redhat.com>
* linux-nat.c (save_sigtrap): Check for breakpoints before
checking watchpoints.
(status_callback) [USE_SIGTRAP_SIGINFO]: Don't check whether a
breakpoint is inserted if relying on SIGTRAP's siginfo.si_code.
(check_stopped_by_breakpoint) [USE_SIGTRAP_SIGINFO]: Decide whether
a breakpoint triggered based on the SIGTRAP's siginfo.si_code.
(linux_nat_stopped_by_sw_breakpoint)
(linux_nat_supports_stopped_by_sw_breakpoint)
(linux_nat_stopped_by_hw_breakpoint)
(linux_nat_supports_stopped_by_hw_breakpoint): New functions.
(linux_nat_wait_1): Don't re-increment the PC if relying on
SIGTRAP's siginfo->si_code.
(linux_nat_add_target): Install new target methods.
* linux-thread-db.c (check_event): Don't account for breakpoint PC
offset if the target already adjusted the PC.
* nat/linux-ptrace.h (USE_SIGTRAP_SIGINFO): New.
(GDB_ARCH_TRAP_BRKPT): New.
(TRAP_HWBKPT): Define if not already defined.
2015-03-04 Pedro Alves <palves@redhat.com>
* NEWS: Mention the new "swbreak" and "hwbreak" stop reasons.

View File

@ -2399,11 +2399,19 @@ save_sigtrap (struct lwp_info *lp)
gdb_assert (lp->stop_reason == TARGET_STOPPED_BY_NO_REASON);
gdb_assert (lp->status != 0);
if (check_stopped_by_watchpoint (lp))
return;
/* Check first if this was a SW/HW breakpoint before checking
watchpoints, because at least s390 can't tell the data address of
hardware watchpoint hits, and the kernel returns
stopped-by-watchpoint as long as there's a watchpoint set. */
if (linux_nat_status_is_event (lp->status))
check_stopped_by_breakpoint (lp);
/* Note that TRAP_HWBKPT can indicate either a hardware breakpoint
or hardware watchpoint. Check which is which if we got
TARGET_STOPPED_BY_HW_BREAKPOINT. */
if (lp->stop_reason == TARGET_STOPPED_BY_NO_REASON
|| lp->stop_reason == TARGET_STOPPED_BY_HW_BREAKPOINT)
check_stopped_by_watchpoint (lp);
}
/* Returns true if the LWP had stopped for a watchpoint. */
@ -2557,6 +2565,8 @@ status_callback (struct lwp_info *lp, void *data)
paddress (target_gdbarch (), pc));
discard = 1;
}
#if !USE_SIGTRAP_SIGINFO
else if (!breakpoint_inserted_here_p (get_regcache_aspace (regcache), pc))
{
if (debug_linux_nat)
@ -2567,6 +2577,7 @@ status_callback (struct lwp_info *lp, void *data)
discard = 1;
}
#endif
if (discard)
{
@ -2669,10 +2680,49 @@ check_stopped_by_breakpoint (struct lwp_info *lp)
struct gdbarch *gdbarch = get_regcache_arch (regcache);
CORE_ADDR pc;
CORE_ADDR sw_bp_pc;
#if USE_SIGTRAP_SIGINFO
siginfo_t siginfo;
#endif
pc = regcache_read_pc (regcache);
sw_bp_pc = pc - target_decr_pc_after_break (gdbarch);
#if USE_SIGTRAP_SIGINFO
if (linux_nat_get_siginfo (lp->ptid, &siginfo))
{
if (siginfo.si_signo == SIGTRAP)
{
if (siginfo.si_code == GDB_ARCH_TRAP_BRKPT)
{
if (debug_linux_nat)
fprintf_unfiltered (gdb_stdlog,
"CSBB: Push back software "
"breakpoint for %s\n",
target_pid_to_str (lp->ptid));
/* Back up the PC if necessary. */
if (pc != sw_bp_pc)
regcache_write_pc (regcache, sw_bp_pc);
lp->stop_pc = sw_bp_pc;
lp->stop_reason = TARGET_STOPPED_BY_SW_BREAKPOINT;
return 1;
}
else if (siginfo.si_code == TRAP_HWBKPT)
{
if (debug_linux_nat)
fprintf_unfiltered (gdb_stdlog,
"CSBB: Push back hardware "
"breakpoint/watchpoint for %s\n",
target_pid_to_str (lp->ptid));
lp->stop_pc = pc;
lp->stop_reason = TARGET_STOPPED_BY_HW_BREAKPOINT;
return 1;
}
}
}
#else
if ((!lp->step || lp->stop_pc == sw_bp_pc)
&& software_breakpoint_inserted_here_p (get_regcache_aspace (regcache),
sw_bp_pc))
@ -2704,10 +2754,53 @@ check_stopped_by_breakpoint (struct lwp_info *lp)
lp->stop_reason = TARGET_STOPPED_BY_HW_BREAKPOINT;
return 1;
}
#endif
return 0;
}
/* Returns true if the LWP had stopped for a software breakpoint. */
static int
linux_nat_stopped_by_sw_breakpoint (struct target_ops *ops)
{
struct lwp_info *lp = find_lwp_pid (inferior_ptid);
gdb_assert (lp != NULL);
return lp->stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT;
}
/* Implement the supports_stopped_by_sw_breakpoint method. */
static int
linux_nat_supports_stopped_by_sw_breakpoint (struct target_ops *ops)
{
return USE_SIGTRAP_SIGINFO;
}
/* Returns true if the LWP had stopped for a hardware
breakpoint/watchpoint. */
static int
linux_nat_stopped_by_hw_breakpoint (struct target_ops *ops)
{
struct lwp_info *lp = find_lwp_pid (inferior_ptid);
gdb_assert (lp != NULL);
return lp->stop_reason == TARGET_STOPPED_BY_HW_BREAKPOINT;
}
/* Implement the supports_stopped_by_hw_breakpoint method. */
static int
linux_nat_supports_stopped_by_hw_breakpoint (struct target_ops *ops)
{
return USE_SIGTRAP_SIGINFO;
}
/* Select one LWP out of those that have events pending. */
static void
@ -3360,8 +3453,10 @@ linux_nat_wait_1 (struct target_ops *ops,
gdb_assert (lp != NULL);
/* Now that we've selected our final event LWP, un-adjust its PC if
it was a software breakpoint. */
if (lp->stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT)
it was a software breakpoint, and we can't reliably support the
"stopped by software breakpoint" stop reason. */
if (lp->stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT
&& !USE_SIGTRAP_SIGINFO)
{
struct regcache *regcache = get_thread_regcache (lp->ptid);
struct gdbarch *gdbarch = get_regcache_arch (regcache);
@ -4651,6 +4746,10 @@ linux_nat_add_target (struct target_ops *t)
t->to_thread_address_space = linux_nat_thread_address_space;
t->to_stopped_by_watchpoint = linux_nat_stopped_by_watchpoint;
t->to_stopped_data_address = linux_nat_stopped_data_address;
t->to_stopped_by_sw_breakpoint = linux_nat_stopped_by_sw_breakpoint;
t->to_supports_stopped_by_sw_breakpoint = linux_nat_supports_stopped_by_sw_breakpoint;
t->to_stopped_by_hw_breakpoint = linux_nat_stopped_by_hw_breakpoint;
t->to_supports_stopped_by_hw_breakpoint = linux_nat_supports_stopped_by_hw_breakpoint;
t->to_can_async_p = linux_nat_can_async_p;
t->to_is_async_p = linux_nat_is_async_p;

View File

@ -1437,8 +1437,10 @@ check_event (ptid_t ptid)
info = get_thread_db_info (ptid_get_pid (ptid));
/* Bail out early if we're not at a thread event breakpoint. */
stop_pc = regcache_read_pc (regcache)
- target_decr_pc_after_break (gdbarch);
stop_pc = regcache_read_pc (regcache);
if (!target_supports_stopped_by_sw_breakpoint ())
stop_pc -= target_decr_pc_after_break (gdbarch);
if (stop_pc != info->td_create_bp_addr
&& stop_pc != info->td_death_bp_addr)
return;

View File

@ -88,6 +88,57 @@ struct buffer;
#define __WALL 0x40000000 /* Wait for any child. */
#endif
/* True if whether a breakpoint/watchpoint triggered can be determined
from the si_code of SIGTRAP's siginfo_t (TRAP_BRKPT/TRAP_HWBKPT).
That is, if the kernel can tell us whether the thread executed a
software breakpoint, we trust it. The kernel will be determining
that from the hardware (e.g., from which exception was raised in
the CPU). Relying on whether a breakpoint is planted in memory at
the time the SIGTRAP is processed to determine whether the thread
stopped for a software breakpoint can be too late. E.g., the
breakpoint could have been removed since. Or the thread could have
stepped an instruction the size of a breakpoint instruction, and
before the stop is processed a breakpoint is inserted at its
address. Getting these wrong is disastrous on decr_pc_after_break
architectures. The moribund location mechanism helps with that
somewhat but it is an heuristic, and can well fail. Getting that
information out of the kernel and ultimately out of the CPU is the
way to go. That said, some architecture may get the si_code wrong,
and as such we're leaving fallback code in place. We'll remove
this after a while if no problem is reported. */
#define USE_SIGTRAP_SIGINFO 1
/* The x86 kernel gets some of the si_code values backwards, like
this:
| what | si_code |
|------------------------------------------+------------|
| software breakpoints (int3) | SI_KERNEL |
| single-steps | TRAP_TRACE |
| single-stepping a syscall | TRAP_BRKPT |
| user sent SIGTRAP | 0 |
| exec SIGTRAP (when no PTRACE_EVENT_EXEC) | 0 |
| hardware breakpoints/watchpoints | TRAP_HWBPT |
That is, it reports SI_KERNEL for software breakpoints (and only
for those), and TRAP_BRKPT for single-stepping a syscall... If the
kernel is ever fixed, we'll just have to detect it like we detect
optional ptrace features: by forking and debugging ourselves,
running to a breakpoint and checking what comes out of
siginfo->si_code.
The generic Linux target code should use GDB_ARCH_TRAP_BRKPT
instead of TRAP_BRKPT to abstract out this x86 peculiarity. */
#if defined __i386__ || defined __x86_64__
# define GDB_ARCH_TRAP_BRKPT SI_KERNEL
#else
# define GDB_ARCH_TRAP_BRKPT TRAP_BRKPT
#endif
#ifndef TRAP_HWBKPT
# define TRAP_HWBKPT 4
#endif
extern void linux_ptrace_attach_fail_reason (pid_t pid, struct buffer *buffer);
/* Find all possible reasons we could have failed to attach to PTID