diff options
Diffstat (limited to 'arch/blackfin/kernel/ptrace.c')
| -rw-r--r-- | arch/blackfin/kernel/ptrace.c | 354 | 
1 files changed, 162 insertions, 192 deletions
diff --git a/arch/blackfin/kernel/ptrace.c b/arch/blackfin/kernel/ptrace.c index 65567dc4b9f..43eb969405d 100644 --- a/arch/blackfin/kernel/ptrace.c +++ b/arch/blackfin/kernel/ptrace.c @@ -1,6 +1,6 @@  /*   * linux/kernel/ptrace.c is by Ross Biro 1/23/92, edited by Linus Torvalds - * these modifications are Copyright 2004-2009 Analog Devices Inc. + * these modifications are Copyright 2004-2010 Analog Devices Inc.   *   * Licensed under the GPL-2   */ @@ -9,10 +9,13 @@  #include <linux/sched.h>  #include <linux/mm.h>  #include <linux/smp.h> +#include <linux/elf.h>  #include <linux/errno.h>  #include <linux/ptrace.h>  #include <linux/user.h> +#include <linux/regset.h>  #include <linux/signal.h> +#include <linux/tracehook.h>  #include <linux/uaccess.h>  #include <asm/page.h> @@ -25,90 +28,57 @@  #include <asm/cacheflush.h>  #include <asm/mem_map.h> -#define TEXT_OFFSET 0  /*   * does not yet catch signals sent when the child dies.   * in exit.c or in signal.c.   */ -/* determines which bits in the SYSCFG reg the user has access to. */ -/* 1 = access 0 = no access */ -#define SYSCFG_MASK 0x0007	/* SYSCFG reg */ -/* sets the trace bits. */ -#define TRACE_BITS 0x0001 - -/* Find the stack offset for a register, relative to thread.esp0. */ -#define PT_REG(reg)	((long)&((struct pt_regs *)0)->reg) - -/* - * Get the address of the live pt_regs for the specified task. - * These are saved onto the top kernel stack when the process - * is not running. - * - * Note: if a user thread is execve'd from kernel space, the - * kernel stack will not be empty on entry to the kernel, so - * ptracing these tasks will fail. - */ -static inline struct pt_regs *get_user_regs(struct task_struct *task) -{ -	return (struct pt_regs *) -	    ((unsigned long)task_stack_page(task) + -	     (THREAD_SIZE - sizeof(struct pt_regs))); -} - -/* - * Get all user integer registers. - */ -static inline int ptrace_getregs(struct task_struct *tsk, void __user *uregs) -{ -	struct pt_regs regs; -	memcpy(®s, get_user_regs(tsk), sizeof(regs)); -	regs.usp = tsk->thread.usp; -	return copy_to_user(uregs, ®s, sizeof(struct pt_regs)) ? -EFAULT : 0; -} - -/* Mapping from PT_xxx to the stack offset at which the register is - * saved.  Notice that usp has no stack-slot and needs to be treated - * specially (see get_reg/put_reg below). - */ -  /*   * Get contents of register REGNO in task TASK.   */ -static inline long get_reg(struct task_struct *task, int regno) +static inline long +get_reg(struct task_struct *task, long regno, unsigned long __user *datap)  { -	unsigned char *reg_ptr; +	long tmp; +	struct pt_regs *regs = task_pt_regs(task); -	struct pt_regs *regs = -	    (struct pt_regs *)((unsigned long)task_stack_page(task) + -			       (THREAD_SIZE - sizeof(struct pt_regs))); -	reg_ptr = (char *)regs; +	if (regno & 3 || regno > PT_LAST_PSEUDO || regno < 0) +		return -EIO;  	switch (regno) { +	case PT_TEXT_ADDR: +		tmp = task->mm->start_code; +		break; +	case PT_TEXT_END_ADDR: +		tmp = task->mm->end_code; +		break; +	case PT_DATA_ADDR: +		tmp = task->mm->start_data; +		break;  	case PT_USP: -		return task->thread.usp; +		tmp = task->thread.usp; +		break;  	default: -		if (regno <= 216) -			return *(long *)(reg_ptr + regno); +		if (regno < sizeof(*regs)) { +			void *reg_ptr = regs; +			tmp = *(long *)(reg_ptr + regno); +		} else +			return -EIO;  	} -	/* slight mystery ... never seems to come here but kernel misbehaves without this code! */ -	printk(KERN_WARNING "Request to get for unknown register %d\n", regno); -	return 0; +	return put_user(tmp, datap);  }  /*   * Write contents of register REGNO in task TASK.   */  static inline int -put_reg(struct task_struct *task, int regno, unsigned long data) +put_reg(struct task_struct *task, long regno, unsigned long data)  { -	char *reg_ptr; +	struct pt_regs *regs = task_pt_regs(task); -	struct pt_regs *regs = -	    (struct pt_regs *)((unsigned long)task_stack_page(task) + -			       (THREAD_SIZE - sizeof(struct pt_regs))); -	reg_ptr = (char *)regs; +	if (regno & 3 || regno > PT_LAST_PSEUDO || regno < 0) +		return -EIO;  	switch (regno) {  	case PT_PC: @@ -125,10 +95,18 @@ put_reg(struct task_struct *task, int regno, unsigned long data)  		regs->usp = data;  		task->thread.usp = data;  		break; +	case PT_SYSCFG:	/* don't let userspace screw with this */ +		if ((data & ~1) != 0x6) +			pr_warning("ptrace: ignore syscfg write of %#lx\n", data); +		break;		/* regs->syscfg = data; break; */  	default: -		if (regno <= 216) -			*(long *)(reg_ptr + regno) = data; +		if (regno < sizeof(*regs)) { +			void *reg_offset = regs; +			*(long *)(reg_offset + regno) = data; +		} +		/* Ignore writes to pseudo registers */  	} +  	return 0;  } @@ -160,24 +138,98 @@ static inline int is_user_addr_valid(struct task_struct *child,  	return -EIO;  } -void ptrace_enable(struct task_struct *child) +/* + * retrieve the contents of Blackfin userspace general registers + */ +static int genregs_get(struct task_struct *target, +		       const struct user_regset *regset, +		       unsigned int pos, unsigned int count, +		       void *kbuf, void __user *ubuf)  { -	unsigned long tmp; -	tmp = get_reg(child, PT_SYSCFG) | (TRACE_BITS); -	put_reg(child, PT_SYSCFG, tmp); +	struct pt_regs *regs = task_pt_regs(target); +	int ret; + +	/* This sucks ... */ +	regs->usp = target->thread.usp; + +	ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, +				  regs, 0, sizeof(*regs)); +	if (ret < 0) +		return ret; + +	return user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, +					sizeof(*regs), -1);  }  /* - * Called by kernel/ptrace.c when detaching.. - * - * Make sure the single step bit is not set. + * update the contents of the Blackfin userspace general registers + */ +static int genregs_set(struct task_struct *target, +		       const struct user_regset *regset, +		       unsigned int pos, unsigned int count, +		       const void *kbuf, const void __user *ubuf) +{ +	struct pt_regs *regs = task_pt_regs(target); +	int ret; + +	/* Don't let people set SYSCFG (it's at the end of pt_regs) */ +	ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, +				 regs, 0, PT_SYSCFG); +	if (ret < 0) +		return ret; + +	/* This sucks ... */ +	target->thread.usp = regs->usp; +	/* regs->retx = regs->pc; */ + +	return user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, +					PT_SYSCFG, -1); +} + +/* + * Define the register sets available on the Blackfin under Linux   */ -void ptrace_disable(struct task_struct *child) +enum bfin_regset { +	REGSET_GENERAL, +}; + +static const struct user_regset bfin_regsets[] = { +	[REGSET_GENERAL] = { +		.core_note_type = NT_PRSTATUS, +		.n              = sizeof(struct pt_regs) / sizeof(long), +		.size           = sizeof(long), +		.align          = sizeof(long), +		.get            = genregs_get, +		.set            = genregs_set, +	}, +}; + +static const struct user_regset_view user_bfin_native_view = { +	.name      = "Blackfin", +	.e_machine = EM_BLACKFIN, +	.regsets   = bfin_regsets, +	.n         = ARRAY_SIZE(bfin_regsets), +}; + +const struct user_regset_view *task_user_regset_view(struct task_struct *task) +{ +	return &user_bfin_native_view; +} + +void user_enable_single_step(struct task_struct *child)  { -	unsigned long tmp; -	/* make sure the single step bit is not set. */ -	tmp = get_reg(child, PT_SYSCFG) & ~TRACE_BITS; -	put_reg(child, PT_SYSCFG, tmp); +	struct pt_regs *regs = task_pt_regs(child); +	regs->syscfg |= SYSCFG_SSSTEP; + +	set_tsk_thread_flag(child, TIF_SINGLESTEP); +} + +void user_disable_single_step(struct task_struct *child) +{ +	struct pt_regs *regs = task_pt_regs(child); +	regs->syscfg &= ~SYSCFG_SSSTEP; + +	clear_tsk_thread_flag(child, TIF_SINGLESTEP);  }  long arch_ptrace(struct task_struct *child, long request, long addr, long data) @@ -240,40 +292,6 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)  			break;  		} -		/* read the word at location addr in the USER area. */ -	case PTRACE_PEEKUSR: -		{ -			unsigned long tmp; -			ret = -EIO; -			tmp = 0; -			if ((addr & 3) || (addr > (sizeof(struct pt_regs) + 16))) { -				printk(KERN_WARNING "ptrace error : PEEKUSR : temporarily returning " -				                    "0 - %x sizeof(pt_regs) is %lx\n", -				     (int)addr, sizeof(struct pt_regs)); -				break; -			} -			if (addr == sizeof(struct pt_regs)) { -				/* PT_TEXT_ADDR */ -				tmp = child->mm->start_code + TEXT_OFFSET; -			} else if (addr == (sizeof(struct pt_regs) + 4)) { -				/* PT_TEXT_END_ADDR */ -				tmp = child->mm->end_code; -			} else if (addr == (sizeof(struct pt_regs) + 8)) { -				/* PT_DATA_ADDR */ -				tmp = child->mm->start_data; -#ifdef CONFIG_BINFMT_ELF_FDPIC -			} else if (addr == (sizeof(struct pt_regs) + 12)) { -				goto case_PTRACE_GETFDPIC_EXEC; -			} else if (addr == (sizeof(struct pt_regs) + 16)) { -				goto case_PTRACE_GETFDPIC_INTERP; -#endif -			} else { -				tmp = get_reg(child, addr); -			} -			ret = put_user(tmp, datap); -			break; -		} -  #ifdef CONFIG_BINFMT_ELF_FDPIC  	case PTRACE_GETFDPIC: {  		unsigned long tmp = 0; @@ -336,78 +354,36 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)  			break;  		} -	case PTRACE_POKEUSR:	/* write the word at location addr in the USER area */ -		ret = -EIO; -		if ((addr & 3) || (addr > (sizeof(struct pt_regs) + 16))) { -			printk(KERN_WARNING "ptrace error : POKEUSR: temporarily returning 0\n"); -			break; -		} - -		if (addr >= (sizeof(struct pt_regs))) { -			ret = 0; -			break; -		} -		if (addr == PT_SYSCFG) { -			data &= SYSCFG_MASK; -			data |= get_reg(child, PT_SYSCFG); +	case PTRACE_PEEKUSR: +		switch (addr) { +#ifdef CONFIG_BINFMT_ELF_FDPIC	/* backwards compat */ +		case PT_FDPIC_EXEC:   goto case_PTRACE_GETFDPIC_EXEC; +		case PT_FDPIC_INTERP: goto case_PTRACE_GETFDPIC_INTERP; +#endif +		default: +			ret = get_reg(child, addr, datap);  		} -		ret = put_reg(child, addr, data); -		break; - -	case PTRACE_SYSCALL:	/* continue and stop at next (return from) syscall */ -	case PTRACE_CONT:	/* restart after signal. */ -		pr_debug("ptrace: syscall/cont\n"); - -		ret = -EIO; -		if (!valid_signal(data)) -			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); -		pr_debug("ptrace: before wake_up_process\n"); -		wake_up_process(child); -		ret = 0; -		break; - -	/* -	 * make the child exit.  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; -		ptrace_disable(child); -		wake_up_process(child); +		pr_debug("ptrace: PEEKUSR reg %li with %#lx = %i\n", addr, data, ret);  		break; -	case PTRACE_SINGLESTEP:	/* set the trap flag. */ -		pr_debug("ptrace: single step\n"); -		ret = -EIO; -		if (!valid_signal(data)) -			break; -		clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); -		ptrace_enable(child); -		child->exit_code = data; -		wake_up_process(child); -		ret = 0; +	case PTRACE_POKEUSR: +		ret = put_reg(child, addr, data); +		pr_debug("ptrace: POKEUSR reg %li with %li = %i\n", addr, data, ret);  		break;  	case PTRACE_GETREGS: -		/* Get all gp regs from the child. */ -		ret = ptrace_getregs(child, datap); -		break; +		pr_debug("ptrace: PTRACE_GETREGS\n"); +		return copy_regset_to_user(child, &user_bfin_native_view, +					   REGSET_GENERAL, +					   0, sizeof(struct pt_regs), +					   (void __user *)data);  	case PTRACE_SETREGS: -		printk(KERN_WARNING "ptrace: SETREGS: **** NOT IMPLEMENTED ***\n"); -		/* Set all gp regs in the child. */ -		ret = 0; -		break; +		pr_debug("ptrace: PTRACE_SETREGS\n"); +		return copy_regset_from_user(child, &user_bfin_native_view, +					     REGSET_GENERAL, +					     0, sizeof(struct pt_regs), +					     (const void __user *)data);  	default:  		ret = ptrace_request(child, request, addr, data); @@ -417,27 +393,21 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)  	return ret;  } -asmlinkage void syscall_trace(void) +asmlinkage int syscall_trace_enter(struct pt_regs *regs)  { -	if (!test_thread_flag(TIF_SYSCALL_TRACE)) -		return; +	int ret = 0; -	if (!(current->ptrace & PT_PTRACED)) -		return; +	if (test_thread_flag(TIF_SYSCALL_TRACE)) +		ret = tracehook_report_syscall_entry(regs); -	/* 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) -				 ? 0x80 : 0)); +	return ret; +} -	/* -	 * 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; -	} +asmlinkage void syscall_trace_leave(struct pt_regs *regs) +{ +	int step; + +	step = test_thread_flag(TIF_SINGLESTEP); +	if (step || test_thread_flag(TIF_SYSCALL_TRACE)) +		tracehook_report_syscall_exit(regs, step);  }  |