/*
* Based on arch/arm/kernel/ptrace.c
*
* By Ross Biro 1/23/92
* edited by Linus Torvalds
* ARM modifications Copyright (C) 2000 Russell King
* Copyright (C) 2012 ARM Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 <linux/compat.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/ptrace.h>
#include <linux/user.h>
#include <linux/security.h>
#include <linux/init.h>
#include <linux/signal.h>
#include <linux/uaccess.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
#include <linux/regset.h>
#include <linux/tracehook.h>
#include <linux/elf.h>
#include <asm/compat.h>
#include <asm/debug-monitors.h>
#include <asm/pgtable.h>
#include <asm/traps.h>
#include <asm/system_misc.h>
#define CREATE_TRACE_POINTS
#include <trace/events/syscalls.h>
/*
* TODO: does not yet catch signals sent when the child dies.
* in exit.c or in signal.c.
*/
/*
* Called by kernel/ptrace.c when detaching..
*/
void ptrace_disable(struct task_struct *child)
{
}
#ifdef CONFIG_HAVE_HW_BREAKPOINT
/*
* Handle hitting a HW-breakpoint.
*/
static void ptrace_hbptriggered(struct perf_event *bp,
struct perf_sample_data *data,
struct pt_regs *regs)
{
struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
siginfo_t info = {
.si_signo = SIGTRAP,
.si_errno = 0,
.si_code = TRAP_HWBKPT,
.si_addr = (void __user *)(bkpt->trigger),
};
#ifdef CONFIG_COMPAT
int i;
if (!is_compat_task())
goto send_sig;
for (i = 0; i < ARM_MAX_BRP; ++i) {
if (current->thread.debug.hbp_break[i] == bp) {
info.si_errno = (i << 1) + 1;
break;
}
}
for (i = ARM_MAX_BRP; i < ARM_MAX_HBP_SLOTS && !bp; ++i) {
if (current->thread.debug.hbp_watch[i] == bp) {
info.si_errno = -((i << 1) + 1);
break;
}
}
send_sig:
#endif
force_sig_info(SIGTRAP, &info, current);
}
/*
* Unregister breakpoints from this task and reset the pointers in
* the thread_struct.
*/
void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
{
int i;
struct thread_struct *t = &tsk->thread;
for (i = 0; i < ARM_MAX_BRP; i++) {
if (t->debug.hbp_break[i]) {
unregister_hw_breakpoint(t->debug.hbp_break[i]);
t->debug.hbp_break[i] = NULL;
}
}
for (i = 0; i < ARM_MAX_WRP; i++) {
if (t->debug.hbp_watch[i]) {
unregister_hw_breakpoint(t->debug.hbp_watch[i]);
t->debug.hbp_watch[i] = NULL;
}
}
}
void ptrace_hw_copy_thread(struct task_struct *tsk)
{
memset(&tsk->thread.debug, 0, sizeof(struct debug_info));
}
static struct perf_event *ptrace_hbp_get_event(unsigned int note_type,
struct task_struct *tsk,
unsigned long idx)
{
struct perf_event *bp = ERR_PTR(-EINVAL);
switch (note_type) {
case NT_ARM_HW_BREAK:
if (idx < ARM_MAX_BRP)
bp = tsk->thread.debug.hbp_break[idx];
break;