+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.
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. */
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)
discard = 1;
}
+#endif
if (discard)
{
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))
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
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);
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;
#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