kprobes/arm: Fix ldrd/strd emulation
authorViktor Rosendahl <viktor.rosendahl@nokia.com>
Mon, 28 Mar 2011 15:56:05 +0000 (18:56 +0300)
committerNicolas Pitre <nicolas.pitre@linaro.org>
Fri, 29 Apr 2011 03:40:54 +0000 (23:40 -0400)
Currently emulate_ldrd and emulate_strd don't even have the adjustment
of the PC value, so in case of Rn == PC, it will not update the PC
incorrectly but instead load/store from the wrong address.  Let's add
both the adjustment of the PC value and the check for PC == PC.

Signed-off-by: Viktor Rosendahl <viktor.rosendahl@nokia.com>
Signed-off-by: Nicolas Pitre <nicolas.pitre@linaro.org>
arch/arm/kernel/kprobes-decode.c

index 23891317dc4be73d083f497291525feefaf015c6..3b0cf90cb44937bedece6403b272d02951cecc30 100644 (file)
@@ -540,9 +540,12 @@ static void __kprobes emulate_ldrd(struct kprobe *p, struct pt_regs *regs)
 {
        insn_2arg_fn_t *i_fn = (insn_2arg_fn_t *)&p->ainsn.insn[0];
        kprobe_opcode_t insn = p->opcode;
+       long ppc = (long)p->addr + 8;
        int rd = (insn >> 12) & 0xf;
        int rn = (insn >> 16) & 0xf;
        int rm = insn & 0xf;  /* rm may be invalid, don't care. */
+       long rmv = (rm == 15) ? ppc : regs->uregs[rm];
+       long rnv = (rn == 15) ? ppc : regs->uregs[rn];
 
        /* Not following the C calling convention here, so need asm(). */
        __asm__ __volatile__ (
@@ -554,29 +557,36 @@ static void __kprobes emulate_ldrd(struct kprobe *p, struct pt_regs *regs)
                "str    r0, %[rn]       \n\t"   /* in case of writeback */
                "str    r2, %[rd0]      \n\t"
                "str    r3, %[rd1]      \n\t"
-               : [rn]  "+m" (regs->uregs[rn]),
+               : [rn]  "+m" (rnv),
                  [rd0] "=m" (regs->uregs[rd]),
                  [rd1] "=m" (regs->uregs[rd+1])
-               : [rm]   "m" (regs->uregs[rm]),
+               : [rm]   "m" (rmv),
                  [cpsr] "r" (regs->ARM_cpsr),
                  [i_fn] "r" (i_fn)
                : "r0", "r1", "r2", "r3", "lr", "cc"
        );
+       if (rn != 15)
+               regs->uregs[rn] = rnv;  /* Save Rn in case of writeback. */
 }
 
 static void __kprobes emulate_strd(struct kprobe *p, struct pt_regs *regs)
 {
        insn_4arg_fn_t *i_fn = (insn_4arg_fn_t *)&p->ainsn.insn[0];
        kprobe_opcode_t insn = p->opcode;
+       long ppc = (long)p->addr + 8;
        int rd = (insn >> 12) & 0xf;
        int rn = (insn >> 16) & 0xf;
        int rm  = insn & 0xf;
-       long rnv = regs->uregs[rn];
-       long rmv = regs->uregs[rm];  /* rm/rmv may be invalid, don't care. */
+       long rnv = (rn == 15) ? ppc : regs->uregs[rn];
+       /* rm/rmv may be invalid, don't care. */
+       long rmv = (rm == 15) ? ppc : regs->uregs[rm];
+       long rnv_wb;
 
-       regs->uregs[rn] = insnslot_4arg_rflags(rnv, rmv, regs->uregs[rd],
+       rnv_wb = insnslot_4arg_rflags(rnv, rmv, regs->uregs[rd],
                                               regs->uregs[rd+1],
                                               regs->ARM_cpsr, i_fn);
+       if (rn != 15)
+               regs->uregs[rn] = rnv_wb;  /* Save Rn in case of writeback. */
 }
 
 static void __kprobes emulate_ldr(struct kprobe *p, struct pt_regs *regs)