powerpc/watchpoint: Don't ignore extraneous exceptions blindly
authorRavi Bangoria <ravi.bangoria@linux.ibm.com>
Thu, 17 Oct 2019 09:32:01 +0000 (15:02 +0530)
committerMichael Ellerman <mpe@ellerman.id.au>
Wed, 13 Nov 2019 05:58:03 +0000 (16:58 +1100)
On powerpc, watchpoint match range is double-word granular. On a
watchpoint hit, DAR is set to the first byte of overlap between actual
access and watched range. And thus it's quite possible that DAR does
not point inside user specified range. Ex, say user creates a
watchpoint with address range 0x1004 to 0x1007. So hw would be
configured to watch from 0x1000 to 0x1007. If there is a 4 byte access
from 0x1002 to 0x1005, DAR will point to 0x1002 and thus interrupt
handler considers it as extraneous, but it's actually not, because
part of the access belongs to what user has asked.

Instead of blindly ignoring the exception, get actual address range by
analysing an instruction, and ignore only if actual range does not
overlap with user specified range.

Note: The behavior is unchanged for 8xx.

Signed-off-by: Ravi Bangoria <ravi.bangoria@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20191017093204.7511-5-ravi.bangoria@linux.ibm.com
arch/powerpc/kernel/hw_breakpoint.c

index f36274d426ed20bdc2057c0e529d969f54932157..58ce3d37c2a3093e68352cb61226259162f12e7e 100644 (file)
@@ -222,33 +222,49 @@ void thread_change_pc(struct task_struct *tsk, struct pt_regs *regs)
        tsk->thread.last_hit_ubp = NULL;
 }
 
-static bool is_larx_stcx_instr(struct pt_regs *regs, unsigned int instr)
+static bool dar_within_range(unsigned long dar, struct arch_hw_breakpoint *info)
 {
-       int ret, type;
-       struct instruction_op op;
+       return ((info->address <= dar) && (dar - info->address < info->len));
+}
 
-       ret = analyse_instr(&op, regs, instr);
-       type = GETTYPE(op.type);
-       return (!ret && (type == LARX || type == STCX));
+static bool
+dar_range_overlaps(unsigned long dar, int size, struct arch_hw_breakpoint *info)
+{
+       return ((dar <= info->address + info->len - 1) &&
+               (dar + size - 1 >= info->address));
 }
 
 /*
  * Handle debug exception notifications.
  */
 static bool stepping_handler(struct pt_regs *regs, struct perf_event *bp,
-                            unsigned long addr)
+                            struct arch_hw_breakpoint *info)
 {
        unsigned int instr = 0;
+       int ret, type, size;
+       struct instruction_op op;
+       unsigned long addr = info->address;
 
        if (__get_user_inatomic(instr, (unsigned int *)regs->nip))
                goto fail;
 
-       if (is_larx_stcx_instr(regs, instr)) {
+       ret = analyse_instr(&op, regs, instr);
+       type = GETTYPE(op.type);
+       size = GETSIZE(op.type);
+
+       if (!ret && (type == LARX || type == STCX)) {
                printk_ratelimited("Breakpoint hit on instruction that can't be emulated."
                                   " Breakpoint at 0x%lx will be disabled.\n", addr);
                goto disable;
        }
 
+       /*
+        * If it's extraneous event, we still need to emulate/single-
+        * step the instruction, but we don't generate an event.
+        */
+       if (size && !dar_range_overlaps(regs->dar, size, info))
+               info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
+
        /* Do not emulate user-space instructions, instead single-step them */
        if (user_mode(regs)) {
                current->thread.last_hit_ubp = bp;
@@ -280,7 +296,6 @@ int hw_breakpoint_handler(struct die_args *args)
        struct perf_event *bp;
        struct pt_regs *regs = args->regs;
        struct arch_hw_breakpoint *info;
-       unsigned long dar = regs->dar;
 
        /* Disable breakpoints during exception handling */
        hw_breakpoint_disable();
@@ -312,19 +327,14 @@ int hw_breakpoint_handler(struct die_args *args)
                goto out;
        }
 
-       /*
-        * Verify if dar lies within the address range occupied by the symbol
-        * being watched to filter extraneous exceptions.  If it doesn't,
-        * we still need to single-step the instruction, but we don't
-        * generate an event.
-        */
        info->type &= ~HW_BRK_TYPE_EXTRANEOUS_IRQ;
-       if (!((bp->attr.bp_addr <= dar) &&
-             (dar - bp->attr.bp_addr < bp->attr.bp_len)))
-               info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
-
-       if (!IS_ENABLED(CONFIG_PPC_8xx) && !stepping_handler(regs, bp, info->address))
-               goto out;
+       if (IS_ENABLED(CONFIG_PPC_8xx)) {
+               if (!dar_within_range(regs->dar, info))
+                       info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
+       } else {
+               if (!stepping_handler(regs, bp, info))
+                       goto out;
+       }
 
        /*
         * As a policy, the callback is invoked in a 'trigger-after-execute'