summaryrefslogtreecommitdiffstats
path: root/arch/sh
diff options
context:
space:
mode:
authorPaul Mundt <lethal@linux-sh.org>2009-12-28 17:53:47 +0900
committerPaul Mundt <lethal@linux-sh.org>2009-12-28 17:53:47 +0900
commit34d0b5af50a063cded842716633501b38ff815fb (patch)
treec729b349fd43e40530a65d073a8476ff754b4b2d /arch/sh
parent22648735405f73299b717bb5933767e9a9c335ca (diff)
sh: Convert ptrace to hw_breakpoint API.
This is the initial step for converting singlestep handling via ptrace over to hw_breakpoints. Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'arch/sh')
-rw-r--r--arch/sh/include/asm/ptrace.h6
-rw-r--r--arch/sh/kernel/hw_breakpoint.c15
-rw-r--r--arch/sh/kernel/ptrace_32.c53
3 files changed, 71 insertions, 3 deletions
diff --git a/arch/sh/include/asm/ptrace.h b/arch/sh/include/asm/ptrace.h
index 1dc12cb44a2..201d11ef211 100644
--- a/arch/sh/include/asm/ptrace.h
+++ b/arch/sh/include/asm/ptrace.h
@@ -124,6 +124,12 @@ struct task_struct;
extern void user_enable_single_step(struct task_struct *);
extern void user_disable_single_step(struct task_struct *);
+struct perf_event;
+struct perf_sample_data;
+
+extern void ptrace_triggered(struct perf_event *bp, int nmi,
+ struct perf_sample_data *data, struct pt_regs *regs);
+
#define task_pt_regs(task) \
((struct pt_regs *) (task_stack_page(task) + THREAD_SIZE) - 1)
diff --git a/arch/sh/kernel/hw_breakpoint.c b/arch/sh/kernel/hw_breakpoint.c
index 022d8ed66bd..c515a3ecf56 100644
--- a/arch/sh/kernel/hw_breakpoint.c
+++ b/arch/sh/kernel/hw_breakpoint.c
@@ -20,6 +20,7 @@
#include <linux/io.h>
#include <asm/hw_breakpoint.h>
#include <asm/mmu_context.h>
+#include <asm/ptrace.h>
struct ubc_context {
unsigned long pc;
@@ -372,7 +373,7 @@ static int __kprobes hw_breakpoint_handler(struct die_args *args)
rcu_read_unlock();
}
- if (bp) {
+ if (bp && bp->overflow_handler != ptrace_triggered) {
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
__raw_writel(UBC_CBR_CE | info->len | info->type, UBC_CBR0);
@@ -387,9 +388,19 @@ static int __kprobes hw_breakpoint_handler(struct die_args *args)
BUILD_TRAP_HANDLER(breakpoint)
{
unsigned long ex = lookup_exception_vector();
+ siginfo_t info;
+ int err;
TRAP_HANDLER_DECL;
- notify_die(DIE_BREAKPOINT, "breakpoint", regs, 0, ex, SIGTRAP);
+ err = notify_die(DIE_BREAKPOINT, "breakpoint", regs, 0, ex, SIGTRAP);
+ if (err == NOTIFY_STOP)
+ return;
+
+ /* Deliver the signal to userspace */
+ info.si_signo = SIGTRAP;
+ info.si_errno = 0;
+ info.si_code = TRAP_HWBKPT;
+ force_sig_info(SIGTRAP, &info, current);
}
/*
diff --git a/arch/sh/kernel/ptrace_32.c b/arch/sh/kernel/ptrace_32.c
index bdb10446cba..8e094c4c7bb 100644
--- a/arch/sh/kernel/ptrace_32.c
+++ b/arch/sh/kernel/ptrace_32.c
@@ -2,7 +2,7 @@
* SuperH process tracing
*
* Copyright (C) 1999, 2000 Kaz Kojima & Niibe Yutaka
- * Copyright (C) 2002 - 2008 Paul Mundt
+ * Copyright (C) 2002 - 2009 Paul Mundt
*
* Audit support by Yuichi Nakamura <ynakam@hitachisoft.jp>
*
@@ -26,6 +26,7 @@
#include <linux/tracehook.h>
#include <linux/elf.h>
#include <linux/regset.h>
+#include <linux/hw_breakpoint.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/system.h>
@@ -63,9 +64,59 @@ static inline int put_stack_long(struct task_struct *task, int offset,
return 0;
}
+void ptrace_triggered(struct perf_event *bp, int nmi,
+ struct perf_sample_data *data, struct pt_regs *regs)
+{
+ struct perf_event_attr attr;
+
+ /*
+ * Disable the breakpoint request here since ptrace has defined a
+ * one-shot behaviour for breakpoint exceptions.
+ */
+ attr = bp->attr;
+ attr.disabled = true;
+ modify_user_hw_breakpoint(bp, &attr);
+}
+
+static int set_single_step(struct task_struct *tsk, unsigned long addr)
+{
+ struct thread_struct *thread = &tsk->thread;
+ struct perf_event *bp;
+ struct perf_event_attr attr;
+
+ bp = thread->ptrace_bps[0];
+ if (!bp) {
+ hw_breakpoint_init(&attr);
+
+ attr.bp_addr = addr;
+ attr.bp_len = HW_BREAKPOINT_LEN_2;
+ attr.bp_type = HW_BREAKPOINT_R;
+
+ bp = register_user_hw_breakpoint(&attr, ptrace_triggered, tsk);
+ if (IS_ERR(bp))
+ return PTR_ERR(bp);
+
+ thread->ptrace_bps[0] = bp;
+ } else {
+ int err;
+
+ attr = bp->attr;
+ attr.bp_addr = addr;
+ err = modify_user_hw_breakpoint(bp, &attr);
+ if (unlikely(err))
+ return err;
+ }
+
+ return 0;
+}
+
void user_enable_single_step(struct task_struct *child)
{
+ unsigned long pc = get_stack_long(child, offsetof(struct pt_regs, pc));
+
set_tsk_thread_flag(child, TIF_SINGLESTEP);
+
+ set_single_step(child, pc);
}
void user_disable_single_step(struct task_struct *child)