diff options
Diffstat (limited to 'arch/arm64/kernel/ptrace.c')
| -rw-r--r-- | arch/arm64/kernel/ptrace.c | 156 | 
1 files changed, 93 insertions, 63 deletions
diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index fecdbf7de82..9fde010c945 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -19,6 +19,7 @@   * 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> @@ -41,6 +42,9 @@  #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. @@ -214,31 +218,29 @@ static int ptrace_hbp_fill_attr_ctrl(unsigned int note_type,  {  	int err, len, type, disabled = !ctrl.enabled; -	if (disabled) { -		len = 0; -		type = HW_BREAKPOINT_EMPTY; -	} else { -		err = arch_bp_generic_fields(ctrl, &len, &type); -		if (err) -			return err; - -		switch (note_type) { -		case NT_ARM_HW_BREAK: -			if ((type & HW_BREAKPOINT_X) != type) -				return -EINVAL; -			break; -		case NT_ARM_HW_WATCH: -			if ((type & HW_BREAKPOINT_RW) != type) -				return -EINVAL; -			break; -		default: +	attr->disabled = disabled; +	if (disabled) +		return 0; + +	err = arch_bp_generic_fields(ctrl, &len, &type); +	if (err) +		return err; + +	switch (note_type) { +	case NT_ARM_HW_BREAK: +		if ((type & HW_BREAKPOINT_X) != type)  			return -EINVAL; -		} +		break; +	case NT_ARM_HW_WATCH: +		if ((type & HW_BREAKPOINT_RW) != type) +			return -EINVAL; +		break; +	default: +		return -EINVAL;  	}  	attr->bp_len	= len;  	attr->bp_type	= type; -	attr->disabled	= disabled;  	return 0;  } @@ -519,6 +521,7 @@ static int fpr_set(struct task_struct *target, const struct user_regset *regset,  		return ret;  	target->thread.fpsimd_state.user_fpsimd = newstate; +	fpsimd_flush_task_state(target);  	return ret;  } @@ -636,28 +639,32 @@ static int compat_gpr_get(struct task_struct *target,  	for (i = 0; i < num_regs; ++i) {  		unsigned int idx = start + i; -		void *reg; +		compat_ulong_t reg;  		switch (idx) {  		case 15: -			reg = (void *)&task_pt_regs(target)->pc; +			reg = task_pt_regs(target)->pc;  			break;  		case 16: -			reg = (void *)&task_pt_regs(target)->pstate; +			reg = task_pt_regs(target)->pstate;  			break;  		case 17: -			reg = (void *)&task_pt_regs(target)->orig_x0; +			reg = task_pt_regs(target)->orig_x0;  			break;  		default: -			reg = (void *)&task_pt_regs(target)->regs[idx]; +			reg = task_pt_regs(target)->regs[idx];  		} -		ret = copy_to_user(ubuf, reg, sizeof(compat_ulong_t)); +		if (kbuf) { +			memcpy(kbuf, ®, sizeof(reg)); +			kbuf += sizeof(reg); +		} else { +			ret = copy_to_user(ubuf, ®, sizeof(reg)); +			if (ret) +				break; -		if (ret) -			break; -		else -			ubuf += sizeof(compat_ulong_t); +			ubuf += sizeof(reg); +		}  	}  	return ret; @@ -685,28 +692,33 @@ static int compat_gpr_set(struct task_struct *target,  	for (i = 0; i < num_regs; ++i) {  		unsigned int idx = start + i; -		void *reg; +		compat_ulong_t reg; + +		if (kbuf) { +			memcpy(®, kbuf, sizeof(reg)); +			kbuf += sizeof(reg); +		} else { +			ret = copy_from_user(®, ubuf, sizeof(reg)); +			if (ret) +				return ret; + +			ubuf += sizeof(reg); +		}  		switch (idx) {  		case 15: -			reg = (void *)&newregs.pc; +			newregs.pc = reg;  			break;  		case 16: -			reg = (void *)&newregs.pstate; +			newregs.pstate = reg;  			break;  		case 17: -			reg = (void *)&newregs.orig_x0; +			newregs.orig_x0 = reg;  			break;  		default: -			reg = (void *)&newregs.regs[idx]; +			newregs.regs[idx] = reg;  		} -		ret = copy_from_user(reg, ubuf, sizeof(compat_ulong_t)); - -		if (ret) -			goto out; -		else -			ubuf += sizeof(compat_ulong_t);  	}  	if (valid_user_regs(&newregs.user_regs)) @@ -714,7 +726,6 @@ static int compat_gpr_set(struct task_struct *target,  	else  		ret = -EINVAL; -out:  	return ret;  } @@ -768,6 +779,7 @@ static int compat_vfp_set(struct task_struct *target,  		uregs->fpcr = fpscr & VFP_FPSCR_CTRL_MASK;  	} +	fpsimd_flush_task_state(target);  	return ret;  } @@ -825,6 +837,7 @@ static int compat_ptrace_write_user(struct task_struct *tsk, compat_ulong_t off,  				    compat_ulong_t val)  {  	int ret; +	mm_segment_t old_fs = get_fs();  	if (off & 3 || off >= COMPAT_USER_SZ)  		return -EIO; @@ -832,10 +845,13 @@ static int compat_ptrace_write_user(struct task_struct *tsk, compat_ulong_t off,  	if (off >= sizeof(compat_elf_gregset_t))  		return 0; +	set_fs(KERNEL_DS);  	ret = copy_regset_from_user(tsk, &user_aarch32_view,  				    REGSET_COMPAT_GPR, off,  				    sizeof(compat_ulong_t),  				    &val); +	set_fs(old_fs); +  	return ret;  } @@ -1062,35 +1078,49 @@ long arch_ptrace(struct task_struct *child, long request,  	return ptrace_request(child, request, addr, data);  } -asmlinkage int syscall_trace(int dir, struct pt_regs *regs) +enum ptrace_syscall_dir { +	PTRACE_SYSCALL_ENTER = 0, +	PTRACE_SYSCALL_EXIT, +}; + +static void tracehook_report_syscall(struct pt_regs *regs, +				     enum ptrace_syscall_dir dir)  { +	int regno;  	unsigned long saved_reg; -	if (!test_thread_flag(TIF_SYSCALL_TRACE)) -		return regs->syscallno; - -	if (is_compat_task()) { -		/* AArch32 uses ip (r12) for scratch */ -		saved_reg = regs->regs[12]; -		regs->regs[12] = dir; -	} else { -		/* -		 * Save X7. X7 is used to denote syscall entry/exit: -		 *   X7 = 0 -> entry, = 1 -> exit -		 */ -		saved_reg = regs->regs[7]; -		regs->regs[7] = dir; -	} +	/* +	 * A scratch register (ip(r12) on AArch32, x7 on AArch64) is +	 * used to denote syscall entry/exit: +	 */ +	regno = (is_compat_task() ? 12 : 7); +	saved_reg = regs->regs[regno]; +	regs->regs[regno] = dir; -	if (dir) +	if (dir == PTRACE_SYSCALL_EXIT)  		tracehook_report_syscall_exit(regs, 0);  	else if (tracehook_report_syscall_entry(regs))  		regs->syscallno = ~0UL; -	if (is_compat_task()) -		regs->regs[12] = saved_reg; -	else -		regs->regs[7] = saved_reg; +	regs->regs[regno] = saved_reg; +} + +asmlinkage int syscall_trace_enter(struct pt_regs *regs) +{ +	if (test_thread_flag(TIF_SYSCALL_TRACE)) +		tracehook_report_syscall(regs, PTRACE_SYSCALL_ENTER); + +	if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) +		trace_sys_enter(regs, regs->syscallno);  	return regs->syscallno;  } + +asmlinkage void syscall_trace_exit(struct pt_regs *regs) +{ +	if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) +		trace_sys_exit(regs, regs_return_value(regs)); + +	if (test_thread_flag(TIF_SYSCALL_TRACE)) +		tracehook_report_syscall(regs, PTRACE_SYSCALL_EXIT); +}  | 
