Subject: [Patch 4/4] PPC64-HWBKPT: Enable hw-breakpoints while handling intervening signals

From: "K.Prasad" <[email-masked]>
Date: Mon, 24 May 2010 16:04:19 +0530

A signal delivered between a hw_breakpoint_handler() and the
single_step_dabr_instruction() will not have the breakpoint active during
signal handling (since breakpoint will not be restored through single-stepping
due to absence of MSR_SE bit on the signal frame). Enable breakpoints before
signal delivery and clear them during sigreturn() syscall.

Limitation: Nested hw-breakpoint exceptions (where second exception is raised
inside signal context) will cause a 'double-hit' i.e. the first breakpoint
exception will be taken twice.

Restore hw-breakpoints if the user-context is altered in the signal handler
(causing loss of MSR_SE). Side-effect: 'Double-hit' of breakpoint if the
instruction pointer is unaltered in the new context.

Signed-off-by: K.Prasad <[email-masked]>
---
 arch/powerpc/include/asm/hw_breakpoint.h |    3 +++
 arch/powerpc/kernel/hw_breakpoint.c      |   28 ++++++++++++++++++++++++++++
 arch/powerpc/kernel/signal.c             |    8 ++++++++
 arch/powerpc/kernel/signal_32.c          |   10 ++++++++++
 arch/powerpc/kernel/signal_64.c          |    7 +++++++
 5 files changed, 56 insertions(+)

Index: linux-2.6.ppc64_test/arch/powerpc/include/asm/hw_breakpoint.h
===================================================================
--- linux-2.6.ppc64_test.orig/arch/powerpc/include/asm/hw_breakpoint.h
+++ linux-2.6.ppc64_test/arch/powerpc/include/asm/hw_breakpoint.h
@@ -43,6 +43,9 @@ static inline void hw_breakpoint_disable
 {
 	set_dabr(0);
 }
+extern void sighandler_install_bp(struct task_struct *tsk);
+extern void sigreturn_uninstall_bp(struct task_struct *tsk);
+extern void thread_change_pc(struct task_struct *tsk, unsigned long msr);
 
 #endif	/* CONFIG_HAVE_HW_BREAKPOINT */
 #endif	/* __KERNEL__ */
Index: linux-2.6.ppc64_test/arch/powerpc/kernel/hw_breakpoint.c
===================================================================
--- linux-2.6.ppc64_test.orig/arch/powerpc/kernel/hw_breakpoint.c
+++ linux-2.6.ppc64_test/arch/powerpc/kernel/hw_breakpoint.c
@@ -175,6 +175,34 @@ int arch_validate_hwbkpt_settings(struct
 	return 0;
 }
 
+void sighandler_install_bp(struct task_struct *tsk)
+{
+	struct arch_hw_breakpoint *info;
+
+	if (likely(!tsk->thread.last_hit_ubp))
+		return;
+
+	info = counter_arch_bp(tsk->thread.last_hit_ubp);
+	set_dabr(info->address | info->type | DABR_TRANSLATION);
+}
+
+void sigreturn_uninstall_bp(struct task_struct *tsk)
+{
+	if (unlikely(tsk->thread.last_hit_ubp))
+		set_dabr(0);
+}
+
+void thread_change_pc(struct task_struct *tsk, unsigned long new_msr)
+{
+	/*
+	 * Do not bother to restore breakpoints if single-stepping is not
+	 * cleared. single_step_dabr_instruction() will handle it if MSR_SE
+	 * is set.
+	 */
+	if (!(new_msr & MSR_SE))
+		sighandler_install_bp(tsk);
+}
+
 /*
  * Handle debug exception notifications.
  */
Index: linux-2.6.ppc64_test/arch/powerpc/kernel/signal.c
===================================================================
--- linux-2.6.ppc64_test.orig/arch/powerpc/kernel/signal.c
+++ linux-2.6.ppc64_test/arch/powerpc/kernel/signal.c
@@ -11,6 +11,7 @@
 
 #include <linux/tracehook.h>
 #include <linux/signal.h>
+#include <asm/hw_breakpoint.h>
 #include <asm/uaccess.h>
 #include <asm/unistd.h>
 
@@ -149,6 +150,13 @@ static int do_signal_pending(sigset_t *o
 	if (current->thread.dabr)
 		set_dabr(current->thread.dabr);
 #endif
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+	/*
+	 * Re-enable the breakpoints (if it was previously cleared in
+	 * hw_breakpoint_handler()) for the signal stack.
+	 */
+	sighandler_install_bp(current);
+#endif /* CONFIG_HAVE_HW_BREAKPOINT */
 
 	if (is32) {
         	if (ka.sa.sa_flags & SA_SIGINFO)
Index: linux-2.6.ppc64_test/arch/powerpc/kernel/signal_64.c
===================================================================
--- linux-2.6.ppc64_test.orig/arch/powerpc/kernel/signal_64.c
+++ linux-2.6.ppc64_test/arch/powerpc/kernel/signal_64.c
@@ -33,6 +33,7 @@
 #include <asm/cacheflush.h>
 #include <asm/syscalls.h>
 #include <asm/vdso.h>
+#include <asm/hw_breakpoint.h>
 
 #include "signal.h"
 
@@ -312,6 +313,9 @@ int sys_swapcontext(struct ucontext __us
 		    || __copy_to_user(&old_ctx->uc_sigmask,
 				      &current->blocked, sizeof(sigset_t)))
 			return -EFAULT;
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+		thread_change_pc(current, new_msr);
+#endif /* CONFIG_HAVE_HW_BREAKPOINT */
 	}
 	if (new_ctx == NULL)
 		return 0;
@@ -364,6 +368,9 @@ int sys_rt_sigreturn(unsigned long r3, u
 	if (__copy_from_user(&set, &uc->uc_sigmask, sizeof(set)))
 		goto badframe;
 	restore_sigmask(&set);
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+	sigreturn_uninstall_bp(current);
+#endif /* CONFIG_HAVE_HW_BREAKPOINT */
 	if (restore_sigcontext(regs, NULL, 1, &uc->uc_mcontext))
 		goto badframe;
 
Index: linux-2.6.ppc64_test/arch/powerpc/kernel/signal_32.c
===================================================================
--- linux-2.6.ppc64_test.orig/arch/powerpc/kernel/signal_32.c
+++ linux-2.6.ppc64_test/arch/powerpc/kernel/signal_32.c
@@ -42,6 +42,7 @@
 #include <asm/syscalls.h>
 #include <asm/sigcontext.h>
 #include <asm/vdso.h>
+#include <asm/hw_breakpoint.h>
 #ifdef CONFIG_PPC64
 #include "ppc32.h"
 #include <asm/unistd.h>
@@ -996,6 +997,9 @@ long sys_swapcontext(struct ucontext __u
 		    || put_sigset_t(&old_ctx->uc_sigmask, &current->blocked)
 		    || __put_user(to_user_ptr(mctx), &old_ctx->uc_regs))
 			return -EFAULT;
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+		thread_change_pc(current, new_msr);
+#endif /* CONFIG_HAVE_HW_BREAKPOINT */
 	}
 	if (new_ctx == NULL)
 		return 0;
@@ -1034,6 +1038,9 @@ long sys_rt_sigreturn(int r3, int r4, in
 		(regs->gpr[1] + __SIGNAL_FRAMESIZE + 16);
 	if (!access_ok(VERIFY_READ, rt_sf, sizeof(*rt_sf)))
 		goto bad;
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+	sigreturn_uninstall_bp(current);
+#endif /* CONFIG_HAVE_HW_BREAKPOINT */
 	if (do_setcontext(&rt_sf->uc, regs, 1))
 		goto bad;
 
@@ -1279,6 +1286,9 @@ long sys_sigreturn(int r3, int r4, int r
 #endif
 	restore_sigmask(&set);
 
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+	sigreturn_uninstall_bp(current);
+#endif /* CONFIG_HAVE_HW_BREAKPOINT */
 	sr = (struct mcontext __user *)from_user_ptr(sigctx.regs);
 	addr = sr;
 	if (!access_ok(VERIFY_READ, sr, sizeof(*sr))