|
@@ -528,11 +528,11 @@ static int default_pre_xol_op(struct arch_uprobe *auprobe, struct pt_regs *regs)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int push_ret_address(struct pt_regs *regs, unsigned long ip)
|
|
|
+static int emulate_push_stack(struct pt_regs *regs, unsigned long val)
|
|
|
{
|
|
|
unsigned long new_sp = regs->sp - sizeof_long();
|
|
|
|
|
|
- if (copy_to_user((void __user *)new_sp, &ip, sizeof_long()))
|
|
|
+ if (copy_to_user((void __user *)new_sp, &val, sizeof_long()))
|
|
|
return -EFAULT;
|
|
|
|
|
|
regs->sp = new_sp;
|
|
@@ -566,7 +566,7 @@ static int default_post_xol_op(struct arch_uprobe *auprobe, struct pt_regs *regs
|
|
|
regs->ip += correction;
|
|
|
} else if (auprobe->defparam.fixups & UPROBE_FIX_CALL) {
|
|
|
regs->sp += sizeof_long(); /* Pop incorrect return address */
|
|
|
- if (push_ret_address(regs, utask->vaddr + auprobe->defparam.ilen))
|
|
|
+ if (emulate_push_stack(regs, utask->vaddr + auprobe->defparam.ilen))
|
|
|
return -ERESTART;
|
|
|
}
|
|
|
/* popf; tell the caller to not touch TF */
|
|
@@ -655,7 +655,7 @@ static bool branch_emulate_op(struct arch_uprobe *auprobe, struct pt_regs *regs)
|
|
|
*
|
|
|
* But there is corner case, see the comment in ->post_xol().
|
|
|
*/
|
|
|
- if (push_ret_address(regs, new_ip))
|
|
|
+ if (emulate_push_stack(regs, new_ip))
|
|
|
return false;
|
|
|
} else if (!check_jmp_cond(auprobe, regs)) {
|
|
|
offs = 0;
|
|
@@ -665,6 +665,16 @@ static bool branch_emulate_op(struct arch_uprobe *auprobe, struct pt_regs *regs)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+static bool push_emulate_op(struct arch_uprobe *auprobe, struct pt_regs *regs)
|
|
|
+{
|
|
|
+ unsigned long *src_ptr = (void *)regs + auprobe->push.reg_offset;
|
|
|
+
|
|
|
+ if (emulate_push_stack(regs, *src_ptr))
|
|
|
+ return false;
|
|
|
+ regs->ip += auprobe->push.ilen;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
static int branch_post_xol_op(struct arch_uprobe *auprobe, struct pt_regs *regs)
|
|
|
{
|
|
|
BUG_ON(!branch_is_call(auprobe));
|
|
@@ -703,6 +713,10 @@ static const struct uprobe_xol_ops branch_xol_ops = {
|
|
|
.post_xol = branch_post_xol_op,
|
|
|
};
|
|
|
|
|
|
+static const struct uprobe_xol_ops push_xol_ops = {
|
|
|
+ .emulate = push_emulate_op,
|
|
|
+};
|
|
|
+
|
|
|
/* Returns -ENOSYS if branch_xol_ops doesn't handle this insn */
|
|
|
static int branch_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn)
|
|
|
{
|
|
@@ -750,6 +764,87 @@ static int branch_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/* Returns -ENOSYS if push_xol_ops doesn't handle this insn */
|
|
|
+static int push_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn)
|
|
|
+{
|
|
|
+ u8 opc1 = OPCODE1(insn), reg_offset = 0;
|
|
|
+
|
|
|
+ if (opc1 < 0x50 || opc1 > 0x57)
|
|
|
+ return -ENOSYS;
|
|
|
+
|
|
|
+ if (insn->length > 2)
|
|
|
+ return -ENOSYS;
|
|
|
+ if (insn->length == 2) {
|
|
|
+ /* only support rex_prefix 0x41 (x64 only) */
|
|
|
+#ifdef CONFIG_X86_64
|
|
|
+ if (insn->rex_prefix.nbytes != 1 ||
|
|
|
+ insn->rex_prefix.bytes[0] != 0x41)
|
|
|
+ return -ENOSYS;
|
|
|
+
|
|
|
+ switch (opc1) {
|
|
|
+ case 0x50:
|
|
|
+ reg_offset = offsetof(struct pt_regs, r8);
|
|
|
+ break;
|
|
|
+ case 0x51:
|
|
|
+ reg_offset = offsetof(struct pt_regs, r9);
|
|
|
+ break;
|
|
|
+ case 0x52:
|
|
|
+ reg_offset = offsetof(struct pt_regs, r10);
|
|
|
+ break;
|
|
|
+ case 0x53:
|
|
|
+ reg_offset = offsetof(struct pt_regs, r11);
|
|
|
+ break;
|
|
|
+ case 0x54:
|
|
|
+ reg_offset = offsetof(struct pt_regs, r12);
|
|
|
+ break;
|
|
|
+ case 0x55:
|
|
|
+ reg_offset = offsetof(struct pt_regs, r13);
|
|
|
+ break;
|
|
|
+ case 0x56:
|
|
|
+ reg_offset = offsetof(struct pt_regs, r14);
|
|
|
+ break;
|
|
|
+ case 0x57:
|
|
|
+ reg_offset = offsetof(struct pt_regs, r15);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+#else
|
|
|
+ return -ENOSYS;
|
|
|
+#endif
|
|
|
+ } else {
|
|
|
+ switch (opc1) {
|
|
|
+ case 0x50:
|
|
|
+ reg_offset = offsetof(struct pt_regs, ax);
|
|
|
+ break;
|
|
|
+ case 0x51:
|
|
|
+ reg_offset = offsetof(struct pt_regs, cx);
|
|
|
+ break;
|
|
|
+ case 0x52:
|
|
|
+ reg_offset = offsetof(struct pt_regs, dx);
|
|
|
+ break;
|
|
|
+ case 0x53:
|
|
|
+ reg_offset = offsetof(struct pt_regs, bx);
|
|
|
+ break;
|
|
|
+ case 0x54:
|
|
|
+ reg_offset = offsetof(struct pt_regs, sp);
|
|
|
+ break;
|
|
|
+ case 0x55:
|
|
|
+ reg_offset = offsetof(struct pt_regs, bp);
|
|
|
+ break;
|
|
|
+ case 0x56:
|
|
|
+ reg_offset = offsetof(struct pt_regs, si);
|
|
|
+ break;
|
|
|
+ case 0x57:
|
|
|
+ reg_offset = offsetof(struct pt_regs, di);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ auprobe->push.reg_offset = reg_offset;
|
|
|
+ auprobe->push.ilen = insn->length;
|
|
|
+ auprobe->ops = &push_xol_ops;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* arch_uprobe_analyze_insn - instruction analysis including validity and fixups.
|
|
|
* @mm: the probed address space.
|
|
@@ -771,6 +866,10 @@ int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm,
|
|
|
if (ret != -ENOSYS)
|
|
|
return ret;
|
|
|
|
|
|
+ ret = push_setup_xol_ops(auprobe, &insn);
|
|
|
+ if (ret != -ENOSYS)
|
|
|
+ return ret;
|
|
|
+
|
|
|
/*
|
|
|
* Figure out which fixups default_post_xol_op() will need to perform,
|
|
|
* and annotate defparam->fixups accordingly.
|