aboutsummaryrefslogblamecommitdiffstats
path: root/arch/mn10300/kernel/ptrace.c
blob: d6d6cdc75c523b66e6218d493311aac2afc5f017 (plain) (tree)


























































































































































































































































































































































































                                                                                  
/* MN10300 Process tracing
 *
 * Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd.
 * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
 * Modified by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public Licence
 * as published by the Free Software Foundation; either version
 * 2 of the Licence, or (at your option) any later version.
 */
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
#include <linux/user.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/system.h>
#include <asm/processor.h>
#include <asm/cacheflush.h>
#include <asm/fpu.h>
#include <asm/asm-offsets.h>

/*
 * translate ptrace register IDs into struct pt_regs offsets
 */
static const u8 ptrace_regid_to_frame[] = {
	[PT_A3 << 2]		= REG_A3,
	[PT_A2 << 2]		= REG_A2,
	[PT_D3 << 2]		= REG_D3,
	[PT_D2 << 2]		= REG_D2,
	[PT_MCVF << 2]		= REG_MCVF,
	[PT_MCRL << 2]		= REG_MCRL,
	[PT_MCRH << 2]		= REG_MCRH,
	[PT_MDRQ << 2]		= REG_MDRQ,
	[PT_E1 << 2]		= REG_E1,
	[PT_E0 << 2]		= REG_E0,
	[PT_E7 << 2]		= REG_E7,
	[PT_E6 << 2]		= REG_E6,
	[PT_E5 << 2]		= REG_E5,
	[PT_E4 << 2]		= REG_E4,
	[PT_E3 << 2]		= REG_E3,
	[PT_E2 << 2]		= REG_E2,
	[PT_SP << 2]		= REG_SP,
	[PT_LAR << 2]		= REG_LAR,
	[PT_LIR << 2]		= REG_LIR,
	[PT_MDR << 2]		= REG_MDR,
	[PT_A1 << 2]		= REG_A1,
	[PT_A0 << 2]		= REG_A0,
	[PT_D1 << 2]		= REG_D1,
	[PT_D0 << 2]		= REG_D0,
	[PT_ORIG_D0 << 2]	= REG_ORIG_D0,
	[PT_EPSW << 2]		= REG_EPSW,
	[PT_PC << 2]		= REG_PC,
};

static inline int get_stack_long(struct task_struct *task, int offset)
{
	return *(unsigned long *)
		((unsigned long) task->thread.uregs + offset);
}

/*
 * this routine will put a word on the processes privileged stack.
 * the offset is how far from the base addr as stored in the TSS.
 * this routine assumes that all the privileged stacks are in our
 * data space.
 */
static inline
int put_stack_long(struct task_struct *task, int offset, unsigned long data)
{
	unsigned long stack;

	stack = (unsigned long) task->thread.uregs + offset;
	*(unsigned long *) stack = data;
	return 0;
}

static inline unsigned long get_fpregs(struct fpu_state_struct *buf,
				       struct task_struct *tsk)
{
	return __copy_to_user(buf, &tsk->thread.fpu_state,
			      sizeof(struct fpu_state_struct));
}

static inline unsigned long set_fpregs(struct task_struct *tsk,
				       struct fpu_state_struct *buf)
{
	return __copy_from_user(&tsk->thread.fpu_state, buf,
				sizeof(struct fpu_state_struct));
}

static inline void fpsave_init(struct task_struct *task)
{
	memset(&task->thread.fpu_state, 0, sizeof(struct fpu_state_struct));
}

/*
 * make sure the single step bit is not set
 */
void ptrace_disable(struct task_struct *child)
{
#ifndef CONFIG_MN10300_USING_JTAG
	struct user *dummy = NULL;
	long tmp;

	tmp = get_stack_long(child, (unsigned long) &dummy->regs.epsw);
	tmp &= ~EPSW_T;
	put_stack_long(child, (unsigned long) &dummy->regs.epsw, tmp);
#endif
}

/*
 * set the single step bit
 */
void ptrace_enable(struct task_struct *child)
{
#ifndef CONFIG_MN10300_USING_JTAG
	struct user *dummy = NULL;
	long tmp;

	tmp = get_stack_long(child, (unsigned long) &dummy->regs.epsw);
	tmp |= EPSW_T;
	put_stack_long(child, (unsigned long) &dummy->regs.epsw, tmp);
#endif
}

/*
 * handle the arch-specific side of process tracing
 */
long arch_ptrace(struct task_struct *child, long request, long addr, long data)
{
	struct fpu_state_struct fpu_state;
	int i, ret;

	switch (request) {
	/* read the word at location addr. */
	case PTRACE_PEEKTEXT: {
		unsigned long tmp;
		int copied;

		copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
		ret = -EIO;
		if (copied != sizeof(tmp))
			break;
		ret = put_user(tmp, (unsigned long *) data);
		break;
	}

	/* read the word at location addr. */
	case PTRACE_PEEKDATA: {
		unsigned long tmp;
		int copied;

		copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
		ret = -EIO;
		if (copied != sizeof(tmp))
			break;
		ret = put_user(tmp, (unsigned long *) data);
		break;
	}

	/* read the word at location addr in the USER area. */
	case PTRACE_PEEKUSR: {
		unsigned long tmp;

		ret = -EIO;
		if ((addr & 3) || addr < 0 ||
		    addr > sizeof(struct user) - 3)
			break;

		tmp = 0;  /* Default return condition */
		if (addr < NR_PTREGS << 2)
			tmp = get_stack_long(child,
					     ptrace_regid_to_frame[addr]);
		ret = put_user(tmp, (unsigned long *) data);
		break;
	}

	/* write the word at location addr. */
	case PTRACE_POKETEXT:
	case PTRACE_POKEDATA:
		if (access_process_vm(child, addr, &data, sizeof(data), 1) ==
		    sizeof(data))
			ret = 0;
		else
			ret = -EIO;
		break;

		/* write the word at location addr in the USER area */
	case PTRACE_POKEUSR:
		ret = -EIO;
		if ((addr & 3) || addr < 0 ||
		    addr > sizeof(struct user) - 3)
			break;

		ret = 0;
		if (addr < NR_PTREGS << 2)
			ret = put_stack_long(child, ptrace_regid_to_frame[addr],
					     data);
		break;

		/* continue and stop at next (return from) syscall */
	case PTRACE_SYSCALL:
		/* restart after signal. */
	case PTRACE_CONT:
		ret = -EIO;
		if ((unsigned long) data > _NSIG)
			break;
		if (request == PTRACE_SYSCALL)
			set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
		else
			clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
		child->exit_code = data;
		ptrace_disable(child);
		wake_up_process(child);
		ret = 0;
		break;

		/*
		 * make the child exit
		 * - the best I can do is send it a sigkill
		 * - perhaps it should be put in the status that it wants to
		 *   exit
		 */
	case PTRACE_KILL:
		ret = 0;
		if (child->exit_state == EXIT_ZOMBIE)	/* already dead */
			break;
		child->exit_code = SIGKILL;
		clear_tsk_thread_flag(child, TIF_SINGLESTEP);
		ptrace_disable(child);
		wake_up_process(child);
		break;

	case PTRACE_SINGLESTEP: /* set the trap flag. */
#ifndef CONFIG_MN10300_USING_JTAG
		ret = -EIO;
		if ((unsigned long) data > _NSIG)
			break;
		clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
		ptrace_enable(child);
		child->exit_code = data;
		wake_up_process(child);
		ret = 0;
#else
		ret = -EINVAL;
#endif
		break;

	case PTRACE_DETACH:	/* detach a process that was attached. */
		ret = ptrace_detach(child, data);
		break;

		/* Get all gp regs from the child. */
	case PTRACE_GETREGS: {
		unsigned long tmp;

		if (!access_ok(VERIFY_WRITE, (unsigned *) data, NR_PTREGS << 2)) {
			ret = -EIO;
			break;
		}

		for (i = 0; i < NR_PTREGS << 2; i += 4) {
			tmp = get_stack_long(child, ptrace_regid_to_frame[i]);
			__put_user(tmp, (unsigned long *) data);
			data += sizeof(tmp);
		}
		ret = 0;
		break;
	}

	case PTRACE_SETREGS: { /* Set all gp regs in the child. */
		unsigned long tmp;

		if (!access_ok(VERIFY_READ, (unsigned long *)data,
			       sizeof(struct pt_regs))) {
			ret = -EIO;
			break;
		}

		for (i = 0; i < NR_PTREGS << 2; i += 4) {
			__get_user(tmp, (unsigned long *) data);
			put_stack_long(child, ptrace_regid_to_frame[i], tmp);
			data += sizeof(tmp);
		}
		ret = 0;
		break;
	}

	case PTRACE_GETFPREGS: { /* Get the child FPU state. */
		if (is_using_fpu(child)) {
			unlazy_fpu(child);
			fpu_state = child->thread.fpu_state;
		} else {
			memset(&fpu_state, 0, sizeof(fpu_state));
		}

		ret = -EIO;
		if (copy_to_user((void *) data, &fpu_state,
				 sizeof(fpu_state)) == 0)
			ret = 0;
		break;
	}

	case PTRACE_SETFPREGS: { /* Set the child FPU state. */
		ret = -EFAULT;
		if (copy_from_user(&fpu_state, (const void *) data,
				   sizeof(fpu_state)) == 0) {
			fpu_kill_state(child);
			child->thread.fpu_state = fpu_state;
			set_using_fpu(child);
			ret = 0;
		}
		break;
	}

	case PTRACE_SETOPTIONS: {
		if (data & PTRACE_O_TRACESYSGOOD)
			child->ptrace |= PT_TRACESYSGOOD;
		else
			child->ptrace &= ~PT_TRACESYSGOOD;
		ret = 0;
		break;
	}

	default:
		ret = -EIO;
		break;
	}

	return ret;
}

/*
 * notification of system call entry/exit
 * - triggered by current->work.syscall_trace
 */
asmlinkage void do_syscall_trace(struct pt_regs *regs, int entryexit)
{
#if 0
	/* just in case... */
	printk(KERN_DEBUG "[%d] syscall_%lu(%lx,%lx,%lx,%lx) = %lx\n",
	       current->pid,
	       regs->orig_d0,
	       regs->a0,
	       regs->d1,
	       regs->a3,
	       regs->a2,
	       regs->d0);
	return;
#endif

	if (!test_thread_flag(TIF_SYSCALL_TRACE) &&
	    !test_thread_flag(TIF_SINGLESTEP))
		return;
	if (!(current->ptrace & PT_PTRACED))
		return;

	/* the 0x80 provides a way for the tracing parent to distinguish
	   between a syscall stop and SIGTRAP delivery */
	ptrace_notify(SIGTRAP |
		      ((current->ptrace & PT_TRACESYSGOOD) &&
		       !test_thread_flag(TIF_SINGLESTEP) ? 0x80 : 0));

	/*
	 * this isn't the same as continuing with a signal, but it will do
	 * for normal use.  strace only continues with a signal if the
	 * stopping signal is not SIGTRAP.  -brl
	 */
	if (current->exit_code) {
		send_sig(current->exit_code, current, 1);
		current->exit_code = 0;
	}
}