Commit 22c9e582 authored by Douglas Anderson's avatar Douglas Anderson Committed by Russell King

ARM: 8997/2: hw_breakpoint: Handle inexact watchpoint addresses

This is commit fdfeff0f ("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@changeidSigned-off-by: default avatarDouglas Anderson <dianders@chromium.org>
Reviewed-by: default avatarMatthias Kaehlcke <mka@chromium.org>
Acked-by: default avatarWill Deacon <will@kernel.org>
Signed-off-by: default avatarRussell King <rmk+kernel@armlinux.org.uk>
parent f75aef39
...@@ -683,6 +683,40 @@ static void disable_single_step(struct perf_event *bp) ...@@ -683,6 +683,40 @@ static void disable_single_step(struct perf_event *bp)
arch_install_hw_breakpoint(bp); arch_install_hw_breakpoint(bp);
} }
/*
* Arm32 hardware does not always report a watchpoint hit address that matches
* one of the watchpoints set. It can also report an address "near" the
* watchpoint if a single instruction access both watched and unwatched
* addresses. There is no straight-forward way, short of disassembling the
* offending instruction, to map that address back to the watchpoint. This
* function computes the distance of the memory access from the watchpoint as a
* heuristic for the likelyhood that a given access triggered the watchpoint.
*
* See this same function in the arm64 platform code, which has the same
* problem.
*
* The function returns the distance of the address from the bytes watched by
* the watchpoint. In case of an exact match, it returns 0.
*/
static u32 get_distance_from_watchpoint(unsigned long addr, u32 val,
struct arch_hw_breakpoint_ctrl *ctrl)
{
u32 wp_low, wp_high;
u32 lens, lene;
lens = __ffs(ctrl->len);
lene = __fls(ctrl->len);
wp_low = val + lens;
wp_high = val + lene;
if (addr < wp_low)
return wp_low - addr;
else if (addr > wp_high)
return addr - wp_high;
else
return 0;
}
static int watchpoint_fault_on_uaccess(struct pt_regs *regs, static int watchpoint_fault_on_uaccess(struct pt_regs *regs,
struct arch_hw_breakpoint *info) struct arch_hw_breakpoint *info)
{ {
...@@ -692,23 +726,25 @@ static int watchpoint_fault_on_uaccess(struct pt_regs *regs, ...@@ -692,23 +726,25 @@ static int watchpoint_fault_on_uaccess(struct pt_regs *regs,
static void watchpoint_handler(unsigned long addr, unsigned int fsr, static void watchpoint_handler(unsigned long addr, unsigned int fsr,
struct pt_regs *regs) struct pt_regs *regs)
{ {
int i, access; int i, access, closest_match = 0;
u32 val, ctrl_reg, alignment_mask; u32 min_dist = -1, dist;
u32 val, ctrl_reg;
struct perf_event *wp, **slots; struct perf_event *wp, **slots;
struct arch_hw_breakpoint *info; struct arch_hw_breakpoint *info;
struct arch_hw_breakpoint_ctrl ctrl; struct arch_hw_breakpoint_ctrl ctrl;
slots = this_cpu_ptr(wp_on_reg); slots = this_cpu_ptr(wp_on_reg);
/*
* Find all watchpoints that match the reported address. If no exact
* match is found. Attribute the hit to the closest watchpoint.
*/
rcu_read_lock();
for (i = 0; i < core_num_wrps; ++i) { for (i = 0; i < core_num_wrps; ++i) {
rcu_read_lock();
wp = slots[i]; wp = slots[i];
if (wp == NULL) if (wp == NULL)
goto unlock; continue;
info = counter_arch_bp(wp);
/* /*
* The DFAR is an unknown value on debug architectures prior * The DFAR is an unknown value on debug architectures prior
* to 7.1. Since we only allow a single watchpoint on these * 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, ...@@ -717,33 +753,31 @@ static void watchpoint_handler(unsigned long addr, unsigned int fsr,
*/ */
if (debug_arch < ARM_DEBUG_ARCH_V7_1) { if (debug_arch < ARM_DEBUG_ARCH_V7_1) {
BUG_ON(i > 0); BUG_ON(i > 0);
info = counter_arch_bp(wp);
info->trigger = wp->attr.bp_addr; info->trigger = wp->attr.bp_addr;
} else { } else {
if (info->ctrl.len == ARM_BREAKPOINT_LEN_8)
alignment_mask = 0x7;
else
alignment_mask = 0x3;
/* Check if the watchpoint value matches. */
val = read_wb_reg(ARM_BASE_WVR + i);
if (val != (addr & ~alignment_mask))
goto unlock;
/* Possible match, check the byte address select. */
ctrl_reg = read_wb_reg(ARM_BASE_WCR + i);
decode_ctrl_reg(ctrl_reg, &ctrl);
if (!((1 << (addr & alignment_mask)) & ctrl.len))
goto unlock;
/* Check that the access type matches. */ /* Check that the access type matches. */
if (debug_exception_updates_fsr()) { if (debug_exception_updates_fsr()) {
access = (fsr & ARM_FSR_ACCESS_MASK) ? access = (fsr & ARM_FSR_ACCESS_MASK) ?
HW_BREAKPOINT_W : HW_BREAKPOINT_R; HW_BREAKPOINT_W : HW_BREAKPOINT_R;
if (!(access & hw_breakpoint_type(wp))) if (!(access & hw_breakpoint_type(wp)))
goto unlock; continue;
} }
val = read_wb_reg(ARM_BASE_WVR + i);
ctrl_reg = read_wb_reg(ARM_BASE_WCR + i);
decode_ctrl_reg(ctrl_reg, &ctrl);
dist = get_distance_from_watchpoint(addr, val, &ctrl);
if (dist < min_dist) {
min_dist = dist;
closest_match = i;
}
/* Is this an exact match? */
if (dist != 0)
continue;
/* We have a winner. */ /* We have a winner. */
info = counter_arch_bp(wp);
info->trigger = addr; info->trigger = addr;
} }
...@@ -765,13 +799,23 @@ static void watchpoint_handler(unsigned long addr, unsigned int fsr, ...@@ -765,13 +799,23 @@ static void watchpoint_handler(unsigned long addr, unsigned int fsr,
* we can single-step over the watchpoint trigger. * we can single-step over the watchpoint trigger.
*/ */
if (!is_default_overflow_handler(wp)) if (!is_default_overflow_handler(wp))
goto unlock; continue;
step: step:
enable_single_step(wp, instruction_pointer(regs)); enable_single_step(wp, instruction_pointer(regs));
unlock:
rcu_read_unlock();
} }
if (min_dist > 0 && min_dist != -1) {
/* No exact match found. */
wp = slots[closest_match];
info = counter_arch_bp(wp);
info->trigger = addr;
pr_debug("watchpoint fired: address = 0x%x\n", info->trigger);
perf_bp_event(wp, regs);
if (is_default_overflow_handler(wp))
enable_single_step(wp, instruction_pointer(regs));
}
rcu_read_unlock();
} }
static void watchpoint_single_step_handler(unsigned long pc) static void watchpoint_single_step_handler(unsigned long pc)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment