ARM64: implement uretprobe for aarch32 mode 13/111113/4
authorVyacheslav Cherkashin <v.cherkashin@samsung.com>
Tue, 12 Jul 2016 11:49:36 +0000 (14:49 +0300)
committerVyacheslav Cherkashin <v.cherkashin@samsung.com>
Tue, 31 Jan 2017 07:21:26 +0000 (10:21 +0300)
Change-Id: I7719016204699bc0378175772d6750b9766bde96
Signed-off-by: Vyacheslav Cherkashin <v.cherkashin@samsung.com>
arch/arm/uprobe/swap_uprobe.c
arch/arm/uprobe/swap_uprobe.h
uprobe/arch/arm64/swap-asm/swap_uprobes.c
uprobe/arch/arm64/swap-asm/swap_uprobes.h
uprobe/swap_uprobes.c
uprobe/swap_uprobes.h

index 3773e25..129771e 100644 (file)
 #include <uprobe/swap_uprobes.h>
 #include <kprobe/swap_kprobes_deps.h>
 #include <kprobe/swap_slots.h>
+#include "../probes/compat_arm64.h"
 
 
+#define PTR_TO_U32(x)  ((u32)(unsigned long)(x))
+#define U32_TO_PTR(x)  ((void *)(unsigned long)(x))
+
 #define flush_insns(addr, size) \
                flush_icache_range((unsigned long)(addr), \
                                   (unsigned long)(addr) + (size))
@@ -120,3 +124,145 @@ void arch_disarm_uprobe_arm(struct uprobe *p, struct task_struct *task)
                flush_insns(vaddr, len);
        }
 }
+
+/**
+ * @brief Prepates uretprobe for ARM.
+ *
+ * @param ri Pointer to the uretprobe instance.
+ * @param regs Pointer to CPU register data.
+ * @return Error code.
+ */
+int prepare_uretprobe_arm(struct uretprobe_instance *ri, struct pt_regs *regs)
+{
+        unsigned long bp_offset;
+
+        bp_offset = thumb_mode(regs) ? 0x1b : 4 * PROBES_TRAMP_RET_BREAK_IDX;
+
+        /* save original return address */
+        ri->ret_addr = (uprobe_opcode_t *)regs->ARM_lr;
+
+        /* replace return address with break point adddress */
+        regs->ARM_lr = (unsigned long)(ri->rp->up.insn) + bp_offset;
+
+        /* save stack pointer address */
+        ri->sp = (uprobe_opcode_t *)regs->ARM_sp;
+
+        /* Set flag of current mode */
+        ri->sp = (uprobe_opcode_t *)((long)ri->sp | !!thumb_mode(regs));
+
+        return 0;
+}
+
+/**
+ * @brief Restores return address.
+ *
+ * @param orig_ret_addr Original return address.
+ * @param regs Pointer to CPU register data.
+ * @return Void.
+ */
+void set_orig_ret_addr_arm(unsigned long orig_ret_addr, struct pt_regs *regs)
+{
+       regs->ARM_lr = orig_ret_addr;
+       regs->ARM_pc = orig_ret_addr & ~0x1;
+
+       if (regs->ARM_lr & 0x1)
+               regs->ARM_cpsr |= PSR_T_BIT;
+       else
+               regs->ARM_cpsr &= ~PSR_T_BIT;
+}
+
+void arch_opcode_analysis_uretprobe_arm(struct uretprobe *rp)
+{
+       /* Remove retprobe if first insn overwrites lr */
+       rp->thumb_noret = noret_thumb(rp->up.opcode);
+       rp->arm_noret = noret_arm(rp->up.opcode);
+}
+
+unsigned long arch_get_trampoline_addr_arm(struct uprobe *p,
+                                          struct pt_regs *regs)
+{
+       return thumb_mode(regs) ?
+                       PTR_TO_U32(p->insn) + 0x1b :
+                       PTR_TO_U32(p->insn +
+                                  PROBES_TRAMP_RET_BREAK_IDX);
+}
+
+unsigned long arch_tramp_by_ri_arm(struct uretprobe_instance *ri)
+{
+       /* Understand function mode */
+       return (PTR_TO_U32(ri->sp) & 1) ?
+                       PTR_TO_U32(ri->rp->up.insn) + 0x1b :
+                       PTR_TO_U32(ri->rp->up.insn +
+                                  PROBES_TRAMP_RET_BREAK_IDX);
+}
+
+int arch_disarm_urp_inst_arm(struct uretprobe_instance *ri,
+                            struct task_struct *task)
+{
+       struct pt_regs *uregs = task_pt_regs(ri->task);
+       u32 ra = uregs->ARM_lr;
+       u32 vaddr, tramp, found = 0;
+       u32 sp = PTR_TO_U32(ri->sp) & ~1;
+       u32 ret_addr = PTR_TO_U32(ri->ret_addr);
+       u32 stack = sp - 4 * (RETPROBE_STACK_DEPTH + 1);
+       u32 buf[RETPROBE_STACK_DEPTH];
+       int i, ret;
+
+       vaddr = PTR_TO_U32(ri->rp->up.addr);
+       tramp = arch_tramp_by_ri_arm(ri);
+
+       /* check stack */
+       ret = read_proc_vm_atomic(task, stack, buf, sizeof(buf));
+       if (ret != sizeof(buf)) {
+               pr_info("---> %s (%d/%d): failed to read stack from %08x\n",
+                       task->comm, task->tgid, task->pid, stack);
+               ret = -EFAULT;
+               goto check_lr;
+       }
+
+       /* search the stack from the bottom */
+       for (i = RETPROBE_STACK_DEPTH - 1; i >= 0; i--) {
+               if (buf[i] == tramp) {
+                       found = stack + 4 * i;
+                       break;
+               }
+       }
+
+       if (!found) {
+               ret = -ESRCH;
+               goto check_lr;
+       }
+
+       pr_info("---> %s (%d/%d): trampoline found at "
+               "%08x (%08x /%+d) - %x, set ret_addr=%08x\n",
+               task->comm, task->tgid, task->pid,
+               found, sp,
+               found - sp, vaddr, ret_addr);
+       ret = write_proc_vm_atomic(task, found, &ret_addr, 4);
+       if (ret != 4) {
+               pr_info("---> %s (%d/%d): failed to write value to %08x",
+                       task->comm, task->tgid, task->pid, found);
+               ret = -EFAULT;
+       } else {
+               ret = 0;
+       }
+
+check_lr: /* check lr anyway */
+       if (ra == tramp) {
+               pr_info("---> %s (%d/%d): trampoline found at "
+                       "lr = %08x - %x, set ret_addr=%08x\n",
+                       task->comm, task->tgid, task->pid, ra, vaddr,
+                       ret_addr);
+
+               /* set ret_addr */
+               uregs->ARM_lr = ret_addr;
+               ret = 0;
+       } else if (ret) {
+               pr_info("---> %s (%d/%d): trampoline NOT found at "
+                       "sp=%08x, lr=%08x - %x, ret_addr=%08x\n",
+                       task->comm, task->tgid, task->pid,
+                       sp, ra, vaddr, ret_addr);
+       }
+
+       return ret;
+}
index ba645b3..4ae91a8 100644 (file)
@@ -87,6 +87,15 @@ int arch_prepare_uprobe_arm(struct uprobe *p);
 int arch_arm_uprobe_arm(struct uprobe *p);
 void arch_disarm_uprobe_arm(struct uprobe *p, struct task_struct *task);
 
+int prepare_uretprobe_arm(struct uretprobe_instance *ri, struct pt_regs *regs);
+void set_orig_ret_addr_arm(unsigned long orig_ret_addr, struct pt_regs *regs);
+void arch_opcode_analysis_uretprobe_arm(struct uretprobe *rp);
+unsigned long arch_get_trampoline_addr_arm(struct uprobe *p,
+                                          struct pt_regs *regs);
+unsigned long arch_tramp_by_ri_arm(struct uretprobe_instance *ri);
+int arch_disarm_urp_inst_arm(struct uretprobe_instance *ri,
+                            struct task_struct *task);
+
 
 #endif /* _SWAP_ASM_ARM_UPROBE_H */
 
index 1be33c0..0abe9d0 100644 (file)
@@ -28,6 +28,7 @@
 #include <kprobe/swap_td_raw.h>
 #include <kprobe/swap_kprobes_deps.h>  /* FIXME: remove it */
 #include <swap-asm/swap_probes.h>
+#include <arch/arm/uprobe/swap_uprobe.h>
 #include <swap-asm/dbg_interface.h>
 #include "uprobes-arm64.h"
 
@@ -93,24 +94,111 @@ static void reset_current_uprobe(void)
 }
 
 
-int arch_prepare_uretprobe(struct uretprobe_instance *ri,
-                          struct pt_regs *regs)
+static int prepare_uretprobe_arm64(struct uretprobe_instance *ri,
+                                  struct pt_regs *regs)
 {
        unsigned long bp_addr = (unsigned long)(ri->rp->up.insn +
                                                URP_RET_BREAK_IDX);
+       unsigned long ret_addr = regs->regs[30] | ARM64_MODE_VADDR_MASK;
 
        ri->sp = (kprobe_opcode_t *)regs->sp;
+       ri->ret_addr = (kprobe_opcode_t *)ret_addr;
 
        /* replace the return address (regs[30] - lr) */
-       ri->ret_addr = (kprobe_opcode_t *)regs->regs[30];
        regs->regs[30] = bp_addr;
 
        return 0;
 }
 
+int arch_prepare_uretprobe(struct uretprobe_instance *ri,
+                          struct pt_regs *regs)
+{
+       if (get_arch_mode((unsigned long)ri->rp->up.addr) == AM_ARM64)
+               return prepare_uretprobe_arm64(ri, regs);
+       else
+               return prepare_uretprobe_arm(ri, regs);
+}
+
+static void arch_opcode_analysis_uretprobe_arm64(struct uretprobe *rp)
+{
+       WARN(1, "not implemented"); /* FIXME: to implement */
+}
+
+void arch_opcode_analysis_uretprobe(struct uretprobe *rp)
+{
+       if (get_arch_mode((unsigned long)rp->up.addr) == AM_ARM64)
+               arch_opcode_analysis_uretprobe_arm64(rp);
+       else
+               arch_opcode_analysis_uretprobe_arm(rp);
+}
+
+static unsigned long arch_get_trampoline_addr_arm64(struct uprobe *p,
+                                                   struct pt_regs *regs)
+{
+       WARN(1, "not implemented"); /* FIXME: to implement */
+       return 0;
+}
+
+/**
+ * @brief Gets trampoline address.
+ *
+ * @param p Pointer to the kprobe.
+ * @param regs Pointer to CPU register data.
+ * @return Trampoline address.
+ */
+unsigned long arch_get_trampoline_addr(struct uprobe *p, struct pt_regs *regs)
+{
+       if (get_arch_mode((unsigned long)p->addr) == AM_ARM64)
+               return arch_get_trampoline_addr_arm64(p, regs);
+       else
+               return arch_get_trampoline_addr_arm(p, regs);
+}
+
+static unsigned long arch_tramp_by_ri_arm64(struct uretprobe_instance *ri)
+{
+       WARN(1, "not implemented"); /* FIXME: to implement */
+       return 0;
+}
+
+unsigned long arch_tramp_by_ri(struct uretprobe_instance *ri)
+{
+       if (get_arch_mode((unsigned long)ri->rp->up.addr) == AM_ARM64)
+               return arch_tramp_by_ri_arm64(ri);
+       else
+               return arch_tramp_by_ri_arm(ri);
+}
+
+static int arch_disarm_urp_inst_arm64(struct uretprobe_instance *ri,
+                                     struct task_struct *task)
+{
+       WARN(1, "not implemented"); /* FIXME: to implement */
+       return 0;
+}
+
+/**
+ * @brief Disarms uretprobe instance.
+ *
+ * @param ri Pointer to the uretprobe instance
+ * @param task Pointer to the task for which the uretprobe instance
+ * @return 0 on success,\n
+ * negative error code on error.
+ */
+int arch_disarm_urp_inst(struct uretprobe_instance *ri,
+                        struct task_struct *task)
+{
+       if (get_arch_mode((unsigned long)ri->rp->up.addr) == AM_ARM64)
+               return arch_disarm_urp_inst_arm64(ri, task);
+       else
+               return arch_disarm_urp_inst_arm(ri, task);
+}
+
 void arch_set_orig_ret_addr(unsigned long orig_ret_addr, struct pt_regs *regs)
 {
-       regs->pc = orig_ret_addr;
+       if (get_arch_mode(orig_ret_addr) == AM_ARM64) {
+               regs->pc = get_real_addr(orig_ret_addr);
+       } else {
+               set_orig_ret_addr_arm(orig_ret_addr, regs);
+       }
 }
 
 static int make_urp_arm64(struct uprobe *p)
@@ -134,8 +222,6 @@ static int make_urp_arm64(struct uprobe *p)
                pr_err("%s failed to write memory %p!\n", __func__, utramp);
        }
 
-       add_uprobe_table(p);
-
        return 0;
 }
 
@@ -285,6 +371,11 @@ int arch_prepare_uprobe(struct uprobe *p)
                ret = arch_prepare_uprobe_arm(p);
        }
 
+       if (!ret) {
+               /* for uretprobe */
+               add_uprobe_table(p);
+       }
+
        return ret;
 }
 
@@ -467,8 +558,37 @@ static void arch_prepare_singlestep(struct uprobe *p, struct pt_regs *regs)
        }
 }
 
+static int urp_handler_aarch32(struct pt_regs *regs, pid_t tgid)
+{
+       struct uprobe *p;
+       unsigned long vaddr = regs->pc;
+       unsigned long offset_bp = compat_thumb_mode(regs) ?
+                                 0x1a :
+                                 4 * PROBES_TRAMP_RET_BREAK_IDX;
+       unsigned long tramp_addr = vaddr - offset_bp;
+       unsigned long flags;
+
+       local_irq_save(flags);
+       p = get_uprobe_by_insn_slot((void *)tramp_addr, tgid, regs);
+       if (unlikely(p == NULL)) {
+               local_irq_restore(flags);
+
+               pr_info("no_uprobe: Not one of ours: let kernel handle it %lx\n",
+                       vaddr);
+               return 1;
+       }
+
+       get_up(p);
+       local_irq_restore(flags);
+       trampoline_uprobe_handler(p, regs);
+       put_up(p);
+
+       return 0;
+}
+
 static int uprobe_handler_aarch32(struct pt_regs *regs, u32 instr)
 {
+       int ret = 0;
        struct uprobe *p;
        unsigned long flags;
        unsigned long vaddr = regs->pc | !!compat_thumb_mode(regs);
@@ -488,9 +608,30 @@ static int uprobe_handler_aarch32(struct pt_regs *regs, u32 instr)
                put_up(p);
        } else {
                local_irq_restore(flags);
+               ret = urp_handler_aarch32(regs, tgid);
+
+               /* check ARM/THUMB CPU mode matches installed probe mode */
+               if (ret == 1) {
+                       vaddr ^= 1;
+
+                       local_irq_save(flags);
+                       p = get_uprobe((uprobe_opcode_t *)vaddr, tgid);
+                       if (p) {
+                               get_up(p);
+                               local_irq_restore(flags);
+                               pr_err("invalid mode: thumb=%d addr=%p insn=%08x\n",
+                                      !!compat_thumb_mode(regs), p->addr, p->opcode);
+                               ret = 0;
+
+                               disarm_uprobe(p, current);
+                               put_up(p);
+                       } else {
+                               local_irq_restore(flags);
+                       }
+               }
        }
 
-       return 0;
+       return ret;
 }
 
 
index 5494e08..ef87fa1 100644 (file)
@@ -24,6 +24,7 @@
 
 
 #include <linux/types.h>
+#include <linux/ptrace.h>
 #include <arch/arm/probes/probes.h>
 
 
@@ -125,29 +126,14 @@ int arch_arm_uprobe(struct uprobe *p);
 void arch_disarm_uprobe(struct uprobe *p, struct task_struct *task);
 
 
-static inline unsigned long arch_get_trampoline_addr(struct uprobe *p,
-                                                    struct pt_regs *regs)
-{
-       WARN(1, "not implemented"); /* FIXME: to implement */
-       return 0;
-}
-
+unsigned long arch_get_trampoline_addr(struct uprobe *p, struct pt_regs *regs);
 void arch_set_orig_ret_addr(unsigned long orig_ret_addr, struct pt_regs *regs);
 int arch_prepare_uretprobe(struct uretprobe_instance *ri,
                           struct pt_regs *regs);
 
-static inline int arch_opcode_analysis_uretprobe(struct uretprobe *rp)
-{
-       WARN(1, "not implemented"); /* FIXME: to implement */
-       return 0;
-}
-
-static inline int arch_disarm_urp_inst(struct uretprobe_instance *ri,
-                                      struct task_struct *task)
-{
-       WARN(1, "not implemented"); /* FIXME: to implement */
-       return 0;
-}
+void arch_opcode_analysis_uretprobe(struct uretprobe *rp);
+int arch_disarm_urp_inst(struct uretprobe_instance *ri,
+                        struct task_struct *task);
 
 static inline void arch_ujprobe_return(void)
 {
index 3ef0857..031dac9 100644 (file)
@@ -752,16 +752,18 @@ int trampoline_uprobe_handler(struct uprobe *p, struct pt_regs *regs)
 static int pre_handler_uretprobe(struct uprobe *p, struct pt_regs *regs)
 {
        struct uretprobe *rp = container_of(p, struct uretprobe, up);
-#ifdef CONFIG_ARM
-       int noret = thumb_mode(regs) ? rp->thumb_noret : rp->arm_noret;
-#endif
        struct uretprobe_instance *ri;
        int ret = 0;
 
-#ifdef CONFIG_ARM
+#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
+# if defined(CONFIG_ARM64)
+#  define thumb_mode(regs)     compat_thumb_mode(regs)
+# endif /* defined(CONFIG_ARM64) */
+       int noret = thumb_mode(regs) ? rp->thumb_noret : rp->arm_noret;
+
        if (noret)
                return 0;
-#endif
+#endif /* defined(CONFIG_ARM) || defined(CONFIG_ARM64) */
 
        /* TODO: consider to only swap the
         * RA after the last pre_handler fired */
index 5554bcd..20d4027 100644 (file)
@@ -150,7 +150,7 @@ struct uretprobe {
        struct hlist_head free_instances;   /**< Free instances list */
        struct hlist_head used_instances;   /**< Used instances list */
 
-#ifdef CONFIG_ARM
+#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
        unsigned arm_noret:1;               /**< No-return flag for ARM */
        unsigned thumb_noret:1;             /**< No-return flag for Thumb */
 #endif