diff options
Diffstat (limited to 'arch/mips/kernel/kprobes.c')
| -rw-r--r-- | arch/mips/kernel/kprobes.c | 177 | 
1 files changed, 148 insertions, 29 deletions
diff --git a/arch/mips/kernel/kprobes.c b/arch/mips/kernel/kprobes.c index ee28683fc2a..158467da9bc 100644 --- a/arch/mips/kernel/kprobes.c +++ b/arch/mips/kernel/kprobes.c @@ -25,10 +25,12 @@  #include <linux/kprobes.h>  #include <linux/preempt.h> +#include <linux/uaccess.h>  #include <linux/kdebug.h>  #include <linux/slab.h>  #include <asm/ptrace.h> +#include <asm/branch.h>  #include <asm/break.h>  #include <asm/inst.h> @@ -112,17 +114,49 @@ insn_ok:  	return 0;  } +/* + * insn_has_ll_or_sc function checks whether instruction is ll or sc + * one; putting breakpoint on top of atomic ll/sc pair is bad idea; + * so we need to prevent it and refuse kprobes insertion for such + * instructions; cannot do much about breakpoint in the middle of + * ll/sc pair; it is upto user to avoid those places + */ +static int __kprobes insn_has_ll_or_sc(union mips_instruction insn) +{ +	int ret = 0; + +	switch (insn.i_format.opcode) { +	case ll_op: +	case lld_op: +	case sc_op: +	case scd_op: +		ret = 1; +		break; +	default: +		break; +	} +	return ret; +} +  int __kprobes arch_prepare_kprobe(struct kprobe *p)  {  	union mips_instruction insn;  	union mips_instruction prev_insn;  	int ret = 0; -	prev_insn = p->addr[-1];  	insn = p->addr[0]; -	if (insn_has_delayslot(insn) || insn_has_delayslot(prev_insn)) { -		pr_notice("Kprobes for branch and jump instructions are not supported\n"); +	if (insn_has_ll_or_sc(insn)) { +		pr_notice("Kprobes for ll and sc instructions are not" +			  "supported\n"); +		ret = -EINVAL; +		goto out; +	} + +	if ((probe_kernel_read(&prev_insn, p->addr - 1, +				sizeof(mips_instruction)) == 0) && +				insn_has_delayslot(prev_insn)) { +		pr_notice("Kprobes for branch delayslot are not supported\n");  		ret = -EINVAL;  		goto out;  	} @@ -138,9 +172,20 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)  	 * In the kprobe->ainsn.insn[] array we store the original  	 * instruction at index zero and a break trap instruction at  	 * index one. +	 * +	 * On MIPS arch if the instruction at probed address is a +	 * branch instruction, we need to execute the instruction at +	 * Branch Delayslot (BD) at the time of probe hit. As MIPS also +	 * doesn't have single stepping support, the BD instruction can +	 * not be executed in-line and it would be executed on SSOL slot +	 * using a normal breakpoint instruction in the next slot. +	 * So, read the instruction and save it for later execution.  	 */ +	if (insn_has_delayslot(insn)) +		memcpy(&p->ainsn.insn[0], p->addr + 1, sizeof(kprobe_opcode_t)); +	else +		memcpy(&p->ainsn.insn[0], p->addr, sizeof(kprobe_opcode_t)); -	memcpy(&p->ainsn.insn[0], p->addr, sizeof(kprobe_opcode_t));  	p->ainsn.insn[1] = breakpoint2_insn;  	p->opcode = *p->addr; @@ -191,16 +236,96 @@ static void set_current_kprobe(struct kprobe *p, struct pt_regs *regs,  	kcb->kprobe_saved_epc = regs->cp0_epc;  } -static void prepare_singlestep(struct kprobe *p, struct pt_regs *regs) +/** + * evaluate_branch_instrucion - + * + * Evaluate the branch instruction at probed address during probe hit. The + * result of evaluation would be the updated epc. The insturction in delayslot + * would actually be single stepped using a normal breakpoint) on SSOL slot. + * + * The result is also saved in the kprobe control block for later use, + * in case we need to execute the delayslot instruction. The latter will be + * false for NOP instruction in dealyslot and the branch-likely instructions + * when the branch is taken. And for those cases we set a flag as + * SKIP_DELAYSLOT in the kprobe control block + */ +static int evaluate_branch_instruction(struct kprobe *p, struct pt_regs *regs, +					struct kprobe_ctlblk *kcb)  { +	union mips_instruction insn = p->opcode; +	long epc; +	int ret = 0; + +	epc = regs->cp0_epc; +	if (epc & 3) +		goto unaligned; + +	if (p->ainsn.insn->word == 0) +		kcb->flags |= SKIP_DELAYSLOT; +	else +		kcb->flags &= ~SKIP_DELAYSLOT; + +	ret = __compute_return_epc_for_insn(regs, insn); +	if (ret < 0) +		return ret; + +	if (ret == BRANCH_LIKELY_TAKEN) +		kcb->flags |= SKIP_DELAYSLOT; + +	kcb->target_epc = regs->cp0_epc; + +	return 0; + +unaligned: +	pr_notice("%s: unaligned epc - sending SIGBUS.\n", current->comm); +	force_sig(SIGBUS, current); +	return -EFAULT; + +} + +static void prepare_singlestep(struct kprobe *p, struct pt_regs *regs, +						struct kprobe_ctlblk *kcb) +{ +	int ret = 0; +  	regs->cp0_status &= ~ST0_IE;  	/* single step inline if the instruction is a break */  	if (p->opcode.word == breakpoint_insn.word ||  	    p->opcode.word == breakpoint2_insn.word)  		regs->cp0_epc = (unsigned long)p->addr; -	else -		regs->cp0_epc = (unsigned long)&p->ainsn.insn[0]; +	else if (insn_has_delayslot(p->opcode)) { +		ret = evaluate_branch_instruction(p, regs, kcb); +		if (ret < 0) { +			pr_notice("Kprobes: Error in evaluating branch\n"); +			return; +		} +	} +	regs->cp0_epc = (unsigned long)&p->ainsn.insn[0]; +} + +/* + * Called after single-stepping.  p->addr is the address of the + * instruction whose first byte has been replaced by the "break 0" + * instruction.  To avoid the SMP problems that can occur when we + * temporarily put back the original opcode to single-step, we + * single-stepped a copy of the instruction.  The address of this + * copy is p->ainsn.insn. + * + * This function prepares to return from the post-single-step + * breakpoint trap. In case of branch instructions, the target + * epc to be restored. + */ +static void __kprobes resume_execution(struct kprobe *p, +				       struct pt_regs *regs, +				       struct kprobe_ctlblk *kcb) +{ +	if (insn_has_delayslot(p->opcode)) +		regs->cp0_epc = kcb->target_epc; +	else { +		unsigned long orig_epc = kcb->kprobe_saved_epc; +		regs->cp0_epc = orig_epc + 4; +	}  }  static int __kprobes kprobe_handler(struct pt_regs *regs) @@ -239,8 +364,13 @@ static int __kprobes kprobe_handler(struct pt_regs *regs)  			save_previous_kprobe(kcb);  			set_current_kprobe(p, regs, kcb);  			kprobes_inc_nmissed_count(p); -			prepare_singlestep(p, regs); +			prepare_singlestep(p, regs, kcb);  			kcb->kprobe_status = KPROBE_REENTER; +			if (kcb->flags & SKIP_DELAYSLOT) { +				resume_execution(p, regs, kcb); +				restore_previous_kprobe(kcb); +				preempt_enable_no_resched(); +			}  			return 1;  		} else {  			if (addr->word != breakpoint_insn.word) { @@ -284,8 +414,16 @@ static int __kprobes kprobe_handler(struct pt_regs *regs)  	}  ss_probe: -	prepare_singlestep(p, regs); -	kcb->kprobe_status = KPROBE_HIT_SS; +	prepare_singlestep(p, regs, kcb); +	if (kcb->flags & SKIP_DELAYSLOT) { +		kcb->kprobe_status = KPROBE_HIT_SSDONE; +		if (p->post_handler) +			p->post_handler(p, regs, 0); +		resume_execution(p, regs, kcb); +		preempt_enable_no_resched(); +	} else +		kcb->kprobe_status = KPROBE_HIT_SS; +  	return 1;  no_kprobe: @@ -294,25 +432,6 @@ no_kprobe:  } -/* - * Called after single-stepping.  p->addr is the address of the - * instruction whose first byte has been replaced by the "break 0" - * instruction.  To avoid the SMP problems that can occur when we - * temporarily put back the original opcode to single-step, we - * single-stepped a copy of the instruction.  The address of this - * copy is p->ainsn.insn. - * - * This function prepares to return from the post-single-step - * breakpoint trap. - */ -static void __kprobes resume_execution(struct kprobe *p, -				       struct pt_regs *regs, -				       struct kprobe_ctlblk *kcb) -{ -	unsigned long orig_epc = kcb->kprobe_saved_epc; -	regs->cp0_epc = orig_epc + 4; -} -  static inline int post_kprobe_handler(struct pt_regs *regs)  {  	struct kprobe *cur = kprobe_running();  |