Skip to content

Commit c0997e1

Browse files
diandersgregkh
authored andcommitted
ARM: 8997/2: hw_breakpoint: Handle inexact watchpoint addresses
[ Upstream commit 22c9e58 ] This is commit fdfeff0 ("arm64: hw_breakpoint: Handle inexact watchpoint addresses") but ported to arm32, which has the same problem. This problem was found by Android CTS tests, notably the "watchpoint_imprecise" test [1]. I tested locally against a copycat (simplified) version of the test though. [1] https://android.googlesource.com/platform/bionic/+/master/tests/sys_ptrace_test.cpp Link: https://lkml.kernel.org/r/20191019111216.1.I82eae759ca6dc28a245b043f485ca490e3015321@changeid Signed-off-by: Douglas Anderson <dianders@chromium.org> Reviewed-by: Matthias Kaehlcke <mka@chromium.org> Acked-by: Will Deacon <will@kernel.org> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent f6a41f7 commit c0997e1

1 file changed

Lines changed: 72 additions & 28 deletions

File tree

arch/arm/kernel/hw_breakpoint.c

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,40 @@ static void disable_single_step(struct perf_event *bp)
683683
arch_install_hw_breakpoint(bp);
684684
}
685685

686+
/*
687+
* Arm32 hardware does not always report a watchpoint hit address that matches
688+
* one of the watchpoints set. It can also report an address "near" the
689+
* watchpoint if a single instruction access both watched and unwatched
690+
* addresses. There is no straight-forward way, short of disassembling the
691+
* offending instruction, to map that address back to the watchpoint. This
692+
* function computes the distance of the memory access from the watchpoint as a
693+
* heuristic for the likelyhood that a given access triggered the watchpoint.
694+
*
695+
* See this same function in the arm64 platform code, which has the same
696+
* problem.
697+
*
698+
* The function returns the distance of the address from the bytes watched by
699+
* the watchpoint. In case of an exact match, it returns 0.
700+
*/
701+
static u32 get_distance_from_watchpoint(unsigned long addr, u32 val,
702+
struct arch_hw_breakpoint_ctrl *ctrl)
703+
{
704+
u32 wp_low, wp_high;
705+
u32 lens, lene;
706+
707+
lens = __ffs(ctrl->len);
708+
lene = __fls(ctrl->len);
709+
710+
wp_low = val + lens;
711+
wp_high = val + lene;
712+
if (addr < wp_low)
713+
return wp_low - addr;
714+
else if (addr > wp_high)
715+
return addr - wp_high;
716+
else
717+
return 0;
718+
}
719+
686720
static int watchpoint_fault_on_uaccess(struct pt_regs *regs,
687721
struct arch_hw_breakpoint *info)
688722
{
@@ -692,23 +726,25 @@ static int watchpoint_fault_on_uaccess(struct pt_regs *regs,
692726
static void watchpoint_handler(unsigned long addr, unsigned int fsr,
693727
struct pt_regs *regs)
694728
{
695-
int i, access;
696-
u32 val, ctrl_reg, alignment_mask;
729+
int i, access, closest_match = 0;
730+
u32 min_dist = -1, dist;
731+
u32 val, ctrl_reg;
697732
struct perf_event *wp, **slots;
698733
struct arch_hw_breakpoint *info;
699734
struct arch_hw_breakpoint_ctrl ctrl;
700735

701736
slots = this_cpu_ptr(wp_on_reg);
702737

738+
/*
739+
* Find all watchpoints that match the reported address. If no exact
740+
* match is found. Attribute the hit to the closest watchpoint.
741+
*/
742+
rcu_read_lock();
703743
for (i = 0; i < core_num_wrps; ++i) {
704-
rcu_read_lock();
705-
706744
wp = slots[i];
707-
708745
if (wp == NULL)
709-
goto unlock;
746+
continue;
710747

711-
info = counter_arch_bp(wp);
712748
/*
713749
* The DFAR is an unknown value on debug architectures prior
714750
* to 7.1. Since we only allow a single watchpoint on these
@@ -717,33 +753,31 @@ static void watchpoint_handler(unsigned long addr, unsigned int fsr,
717753
*/
718754
if (debug_arch < ARM_DEBUG_ARCH_V7_1) {
719755
BUG_ON(i > 0);
756+
info = counter_arch_bp(wp);
720757
info->trigger = wp->attr.bp_addr;
721758
} else {
722-
if (info->ctrl.len == ARM_BREAKPOINT_LEN_8)
723-
alignment_mask = 0x7;
724-
else
725-
alignment_mask = 0x3;
726-
727-
/* Check if the watchpoint value matches. */
728-
val = read_wb_reg(ARM_BASE_WVR + i);
729-
if (val != (addr & ~alignment_mask))
730-
goto unlock;
731-
732-
/* Possible match, check the byte address select. */
733-
ctrl_reg = read_wb_reg(ARM_BASE_WCR + i);
734-
decode_ctrl_reg(ctrl_reg, &ctrl);
735-
if (!((1 << (addr & alignment_mask)) & ctrl.len))
736-
goto unlock;
737-
738759
/* Check that the access type matches. */
739760
if (debug_exception_updates_fsr()) {
740761
access = (fsr & ARM_FSR_ACCESS_MASK) ?
741762
HW_BREAKPOINT_W : HW_BREAKPOINT_R;
742763
if (!(access & hw_breakpoint_type(wp)))
743-
goto unlock;
764+
continue;
744765
}
745766

767+
val = read_wb_reg(ARM_BASE_WVR + i);
768+
ctrl_reg = read_wb_reg(ARM_BASE_WCR + i);
769+
decode_ctrl_reg(ctrl_reg, &ctrl);
770+
dist = get_distance_from_watchpoint(addr, val, &ctrl);
771+
if (dist < min_dist) {
772+
min_dist = dist;
773+
closest_match = i;
774+
}
775+
/* Is this an exact match? */
776+
if (dist != 0)
777+
continue;
778+
746779
/* We have a winner. */
780+
info = counter_arch_bp(wp);
747781
info->trigger = addr;
748782
}
749783

@@ -765,13 +799,23 @@ static void watchpoint_handler(unsigned long addr, unsigned int fsr,
765799
* we can single-step over the watchpoint trigger.
766800
*/
767801
if (!is_default_overflow_handler(wp))
768-
goto unlock;
769-
802+
continue;
770803
step:
771804
enable_single_step(wp, instruction_pointer(regs));
772-
unlock:
773-
rcu_read_unlock();
774805
}
806+
807+
if (min_dist > 0 && min_dist != -1) {
808+
/* No exact match found. */
809+
wp = slots[closest_match];
810+
info = counter_arch_bp(wp);
811+
info->trigger = addr;
812+
pr_debug("watchpoint fired: address = 0x%x\n", info->trigger);
813+
perf_bp_event(wp, regs);
814+
if (is_default_overflow_handler(wp))
815+
enable_single_step(wp, instruction_pointer(regs));
816+
}
817+
818+
rcu_read_unlock();
775819
}
776820

777821
static void watchpoint_single_step_handler(unsigned long pc)

0 commit comments

Comments
 (0)