arm64: Debugging support
This patch adds ptrace, debug monitors and hardware breakpoints support. Signed-off-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Acked-by: Tony Lindgren <tony@atomide.com> Acked-by: Nicolas Pitre <nico@linaro.org> Acked-by: Olof Johansson <olof@lixom.net> Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com> Acked-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
parent
53631b54c8
commit
478fcb2cdb
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (C) 2012 ARM Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef __ASM_DEBUG_MONITORS_H
|
||||
#define __ASM_DEBUG_MONITORS_H
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
#define DBG_ESR_EVT(x) (((x) >> 27) & 0x7)
|
||||
|
||||
/* AArch64 */
|
||||
#define DBG_ESR_EVT_HWBP 0x0
|
||||
#define DBG_ESR_EVT_HWSS 0x1
|
||||
#define DBG_ESR_EVT_HWWP 0x2
|
||||
#define DBG_ESR_EVT_BRK 0x6
|
||||
|
||||
enum debug_el {
|
||||
DBG_ACTIVE_EL0 = 0,
|
||||
DBG_ACTIVE_EL1,
|
||||
};
|
||||
|
||||
/* AArch32 */
|
||||
#define DBG_ESR_EVT_BKPT 0x4
|
||||
#define DBG_ESR_EVT_VECC 0x5
|
||||
|
||||
#define AARCH32_BREAK_ARM 0x07f001f0
|
||||
#define AARCH32_BREAK_THUMB 0xde01
|
||||
#define AARCH32_BREAK_THUMB2_LO 0xf7f0
|
||||
#define AARCH32_BREAK_THUMB2_HI 0xa000
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
struct task_struct;
|
||||
|
||||
#define local_dbg_save(flags) \
|
||||
do { \
|
||||
typecheck(unsigned long, flags); \
|
||||
asm volatile( \
|
||||
"mrs %0, daif // local_dbg_save\n" \
|
||||
"msr daifset, #8" \
|
||||
: "=r" (flags) : : "memory"); \
|
||||
} while (0)
|
||||
|
||||
#define local_dbg_restore(flags) \
|
||||
do { \
|
||||
typecheck(unsigned long, flags); \
|
||||
asm volatile( \
|
||||
"msr daif, %0 // local_dbg_restore\n" \
|
||||
: : "r" (flags) : "memory"); \
|
||||
} while (0)
|
||||
|
||||
#define DBG_ARCH_ID_RESERVED 0 /* In case of ptrace ABI updates. */
|
||||
|
||||
u8 debug_monitors_arch(void);
|
||||
|
||||
void enable_debug_monitors(enum debug_el el);
|
||||
void disable_debug_monitors(enum debug_el el);
|
||||
|
||||
void user_rewind_single_step(struct task_struct *task);
|
||||
void user_fastforward_single_step(struct task_struct *task);
|
||||
|
||||
void kernel_enable_single_step(struct pt_regs *regs);
|
||||
void kernel_disable_single_step(void);
|
||||
int kernel_active_single_step(void);
|
||||
|
||||
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
||||
int reinstall_suspended_bps(struct pt_regs *regs);
|
||||
#else
|
||||
static inline int reinstall_suspended_bps(struct pt_regs *regs)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __ASSEMBLY */
|
||||
#endif /* __KERNEL__ */
|
||||
#endif /* __ASM_DEBUG_MONITORS_H */
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (C) 2012 ARM Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef __ASM_HW_BREAKPOINT_H
|
||||
#define __ASM_HW_BREAKPOINT_H
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
struct arch_hw_breakpoint_ctrl {
|
||||
u32 __reserved : 19,
|
||||
len : 8,
|
||||
type : 2,
|
||||
privilege : 2,
|
||||
enabled : 1;
|
||||
};
|
||||
|
||||
struct arch_hw_breakpoint {
|
||||
u64 address;
|
||||
u64 trigger;
|
||||
struct arch_hw_breakpoint_ctrl ctrl;
|
||||
};
|
||||
|
||||
static inline u32 encode_ctrl_reg(struct arch_hw_breakpoint_ctrl ctrl)
|
||||
{
|
||||
return (ctrl.len << 5) | (ctrl.type << 3) | (ctrl.privilege << 1) |
|
||||
ctrl.enabled;
|
||||
}
|
||||
|
||||
static inline void decode_ctrl_reg(u32 reg,
|
||||
struct arch_hw_breakpoint_ctrl *ctrl)
|
||||
{
|
||||
ctrl->enabled = reg & 0x1;
|
||||
reg >>= 1;
|
||||
ctrl->privilege = reg & 0x3;
|
||||
reg >>= 2;
|
||||
ctrl->type = reg & 0x3;
|
||||
reg >>= 2;
|
||||
ctrl->len = reg & 0xff;
|
||||
}
|
||||
|
||||
/* Breakpoint */
|
||||
#define ARM_BREAKPOINT_EXECUTE 0
|
||||
|
||||
/* Watchpoints */
|
||||
#define ARM_BREAKPOINT_LOAD 1
|
||||
#define ARM_BREAKPOINT_STORE 2
|
||||
#define AARCH64_ESR_ACCESS_MASK (1 << 6)
|
||||
|
||||
/* Privilege Levels */
|
||||
#define AARCH64_BREAKPOINT_EL1 1
|
||||
#define AARCH64_BREAKPOINT_EL0 2
|
||||
|
||||
/* Lengths */
|
||||
#define ARM_BREAKPOINT_LEN_1 0x1
|
||||
#define ARM_BREAKPOINT_LEN_2 0x3
|
||||
#define ARM_BREAKPOINT_LEN_4 0xf
|
||||
#define ARM_BREAKPOINT_LEN_8 0xff
|
||||
|
||||
/* Kernel stepping */
|
||||
#define ARM_KERNEL_STEP_NONE 0
|
||||
#define ARM_KERNEL_STEP_ACTIVE 1
|
||||
#define ARM_KERNEL_STEP_SUSPEND 2
|
||||
|
||||
/*
|
||||
* Limits.
|
||||
* Changing these will require modifications to the register accessors.
|
||||
*/
|
||||
#define ARM_MAX_BRP 16
|
||||
#define ARM_MAX_WRP 16
|
||||
#define ARM_MAX_HBP_SLOTS (ARM_MAX_BRP + ARM_MAX_WRP)
|
||||
|
||||
/* Virtual debug register bases. */
|
||||
#define AARCH64_DBG_REG_BVR 0
|
||||
#define AARCH64_DBG_REG_BCR (AARCH64_DBG_REG_BVR + ARM_MAX_BRP)
|
||||
#define AARCH64_DBG_REG_WVR (AARCH64_DBG_REG_BCR + ARM_MAX_BRP)
|
||||
#define AARCH64_DBG_REG_WCR (AARCH64_DBG_REG_WVR + ARM_MAX_WRP)
|
||||
|
||||
/* Debug register names. */
|
||||
#define AARCH64_DBG_REG_NAME_BVR "bvr"
|
||||
#define AARCH64_DBG_REG_NAME_BCR "bcr"
|
||||
#define AARCH64_DBG_REG_NAME_WVR "wvr"
|
||||
#define AARCH64_DBG_REG_NAME_WCR "wcr"
|
||||
|
||||
/* Accessor macros for the debug registers. */
|
||||
#define AARCH64_DBG_READ(N, REG, VAL) do {\
|
||||
asm volatile("mrs %0, dbg" REG #N "_el1" : "=r" (VAL));\
|
||||
} while (0)
|
||||
|
||||
#define AARCH64_DBG_WRITE(N, REG, VAL) do {\
|
||||
asm volatile("msr dbg" REG #N "_el1, %0" :: "r" (VAL));\
|
||||
} while (0)
|
||||
|
||||
struct task_struct;
|
||||
struct notifier_block;
|
||||
struct perf_event;
|
||||
struct pmu;
|
||||
|
||||
extern int arch_bp_generic_fields(struct arch_hw_breakpoint_ctrl ctrl,
|
||||
int *gen_len, int *gen_type);
|
||||
extern int arch_check_bp_in_kernelspace(struct perf_event *bp);
|
||||
extern int arch_validate_hwbkpt_settings(struct perf_event *bp);
|
||||
extern int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
|
||||
unsigned long val, void *data);
|
||||
|
||||
extern int arch_install_hw_breakpoint(struct perf_event *bp);
|
||||
extern void arch_uninstall_hw_breakpoint(struct perf_event *bp);
|
||||
extern void hw_breakpoint_pmu_read(struct perf_event *bp);
|
||||
extern int hw_breakpoint_slots(int type);
|
||||
|
||||
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
||||
extern void hw_breakpoint_thread_switch(struct task_struct *next);
|
||||
extern void ptrace_hw_copy_thread(struct task_struct *task);
|
||||
#else
|
||||
static inline void hw_breakpoint_thread_switch(struct task_struct *next)
|
||||
{
|
||||
}
|
||||
static inline void ptrace_hw_copy_thread(struct task_struct *task)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
extern struct pmu perf_ops_bp;
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
#endif /* __ASM_BREAKPOINT_H */
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* ARMv8 single-step debug support and mdscr context switching.
|
||||
*
|
||||
* Copyright (C) 2012 ARM Limited
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Will Deacon <will.deacon@arm.com>
|
||||
*/
|
||||
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/hardirq.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/stat.h>
|
||||
|
||||
#include <asm/debug-monitors.h>
|
||||
#include <asm/local.h>
|
||||
#include <asm/cputype.h>
|
||||
#include <asm/system_misc.h>
|
||||
|
||||
/* Low-level stepping controls. */
|
||||
#define DBG_MDSCR_SS (1 << 0)
|
||||
#define DBG_SPSR_SS (1 << 21)
|
||||
|
||||
/* MDSCR_EL1 enabling bits */
|
||||
#define DBG_MDSCR_KDE (1 << 13)
|
||||
#define DBG_MDSCR_MDE (1 << 15)
|
||||
#define DBG_MDSCR_MASK ~(DBG_MDSCR_KDE | DBG_MDSCR_MDE)
|
||||
|
||||
/* Determine debug architecture. */
|
||||
u8 debug_monitors_arch(void)
|
||||
{
|
||||
return read_cpuid(ID_AA64DFR0_EL1) & 0xf;
|
||||
}
|
||||
|
||||
/*
|
||||
* MDSCR access routines.
|
||||
*/
|
||||
static void mdscr_write(u32 mdscr)
|
||||
{
|
||||
unsigned long flags;
|
||||
local_dbg_save(flags);
|
||||
asm volatile("msr mdscr_el1, %0" :: "r" (mdscr));
|
||||
local_dbg_restore(flags);
|
||||
}
|
||||
|
||||
static u32 mdscr_read(void)
|
||||
{
|
||||
u32 mdscr;
|
||||
asm volatile("mrs %0, mdscr_el1" : "=r" (mdscr));
|
||||
return mdscr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allow root to disable self-hosted debug from userspace.
|
||||
* This is useful if you want to connect an external JTAG debugger.
|
||||
*/
|
||||
static u32 debug_enabled = 1;
|
||||
|
||||
static int create_debug_debugfs_entry(void)
|
||||
{
|
||||
debugfs_create_bool("debug_enabled", 0644, NULL, &debug_enabled);
|
||||
return 0;
|
||||
}
|
||||
fs_initcall(create_debug_debugfs_entry);
|
||||
|
||||
static int __init early_debug_disable(char *buf)
|
||||
{
|
||||
debug_enabled = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
early_param("nodebugmon", early_debug_disable);
|
||||
|
||||
/*
|
||||
* Keep track of debug users on each core.
|
||||
* The ref counts are per-cpu so we use a local_t type.
|
||||
*/
|
||||
static DEFINE_PER_CPU(local_t, mde_ref_count);
|
||||
static DEFINE_PER_CPU(local_t, kde_ref_count);
|
||||
|
||||
void enable_debug_monitors(enum debug_el el)
|
||||
{
|
||||
u32 mdscr, enable = 0;
|
||||
|
||||
WARN_ON(preemptible());
|
||||
|
||||
if (local_inc_return(&__get_cpu_var(mde_ref_count)) == 1)
|
||||
enable = DBG_MDSCR_MDE;
|
||||
|
||||
if (el == DBG_ACTIVE_EL1 &&
|
||||
local_inc_return(&__get_cpu_var(kde_ref_count)) == 1)
|
||||
enable |= DBG_MDSCR_KDE;
|
||||
|
||||
if (enable && debug_enabled) {
|
||||
mdscr = mdscr_read();
|
||||
mdscr |= enable;
|
||||
mdscr_write(mdscr);
|
||||
}
|
||||
}
|
||||
|
||||
void disable_debug_monitors(enum debug_el el)
|
||||
{
|
||||
u32 mdscr, disable = 0;
|
||||
|
||||
WARN_ON(preemptible());
|
||||
|
||||
if (local_dec_and_test(&__get_cpu_var(mde_ref_count)))
|
||||
disable = ~DBG_MDSCR_MDE;
|
||||
|
||||
if (el == DBG_ACTIVE_EL1 &&
|
||||
local_dec_and_test(&__get_cpu_var(kde_ref_count)))
|
||||
disable &= ~DBG_MDSCR_KDE;
|
||||
|
||||
if (disable) {
|
||||
mdscr = mdscr_read();
|
||||
mdscr &= disable;
|
||||
mdscr_write(mdscr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* OS lock clearing.
|
||||
*/
|
||||
static void clear_os_lock(void *unused)
|
||||
{
|
||||
asm volatile("msr mdscr_el1, %0" : : "r" (0));
|
||||
isb();
|
||||
asm volatile("msr oslar_el1, %0" : : "r" (0));
|
||||
isb();
|
||||
}
|
||||
|
||||
static int __cpuinit os_lock_notify(struct notifier_block *self,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
int cpu = (unsigned long)data;
|
||||
if (action == CPU_ONLINE)
|
||||
smp_call_function_single(cpu, clear_os_lock, NULL, 1);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block __cpuinitdata os_lock_nb = {
|
||||
.notifier_call = os_lock_notify,
|
||||
};
|
||||
|
||||
static int __cpuinit debug_monitors_init(void)
|
||||
{
|
||||
/* Clear the OS lock. */
|
||||
smp_call_function(clear_os_lock, NULL, 1);
|
||||
clear_os_lock(NULL);
|
||||
|
||||
/* Register hotplug handler. */
|
||||
register_cpu_notifier(&os_lock_nb);
|
||||
return 0;
|
||||
}
|
||||
postcore_initcall(debug_monitors_init);
|
||||
|
||||
/*
|
||||
* Single step API and exception handling.
|
||||
*/
|
||||
static void set_regs_spsr_ss(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long spsr;
|
||||
|
||||
spsr = regs->pstate;
|
||||
spsr &= ~DBG_SPSR_SS;
|
||||
spsr |= DBG_SPSR_SS;
|
||||
regs->pstate = spsr;
|
||||
}
|
||||
|
||||
static void clear_regs_spsr_ss(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long spsr;
|
||||
|
||||
spsr = regs->pstate;
|
||||
spsr &= ~DBG_SPSR_SS;
|
||||
regs->pstate = spsr;
|
||||
}
|
||||
|
||||
static int single_step_handler(unsigned long addr, unsigned int esr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
siginfo_t info;
|
||||
|
||||
/*
|
||||
* If we are stepping a pending breakpoint, call the hw_breakpoint
|
||||
* handler first.
|
||||
*/
|
||||
if (!reinstall_suspended_bps(regs))
|
||||
return 0;
|
||||
|
||||
if (user_mode(regs)) {
|
||||
info.si_signo = SIGTRAP;
|
||||
info.si_errno = 0;
|
||||
info.si_code = TRAP_HWBKPT;
|
||||
info.si_addr = (void __user *)instruction_pointer(regs);
|
||||
force_sig_info(SIGTRAP, &info, current);
|
||||
|
||||
/*
|
||||
* ptrace will disable single step unless explicitly
|
||||
* asked to re-enable it. For other clients, it makes
|
||||
* sense to leave it enabled (i.e. rewind the controls
|
||||
* to the active-not-pending state).
|
||||
*/
|
||||
user_rewind_single_step(current);
|
||||
} else {
|
||||
/* TODO: route to KGDB */
|
||||
pr_warning("Unexpected kernel single-step exception at EL1\n");
|
||||
/*
|
||||
* Re-enable stepping since we know that we will be
|
||||
* returning to regs.
|
||||
*/
|
||||
set_regs_spsr_ss(regs);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init single_step_init(void)
|
||||
{
|
||||
hook_debug_fault_code(DBG_ESR_EVT_HWSS, single_step_handler, SIGTRAP,
|
||||
TRAP_HWBKPT, "single-step handler");
|
||||
return 0;
|
||||
}
|
||||
arch_initcall(single_step_init);
|
||||
|
||||
/* Re-enable single step for syscall restarting. */
|
||||
void user_rewind_single_step(struct task_struct *task)
|
||||
{
|
||||
/*
|
||||
* If single step is active for this thread, then set SPSR.SS
|
||||
* to 1 to avoid returning to the active-pending state.
|
||||
*/
|
||||
if (test_ti_thread_flag(task_thread_info(task), TIF_SINGLESTEP))
|
||||
set_regs_spsr_ss(task_pt_regs(task));
|
||||
}
|
||||
|
||||
void user_fastforward_single_step(struct task_struct *task)
|
||||
{
|
||||
if (test_ti_thread_flag(task_thread_info(task), TIF_SINGLESTEP))
|
||||
clear_regs_spsr_ss(task_pt_regs(task));
|
||||
}
|
||||
|
||||
/* Kernel API */
|
||||
void kernel_enable_single_step(struct pt_regs *regs)
|
||||
{
|
||||
WARN_ON(!irqs_disabled());
|
||||
set_regs_spsr_ss(regs);
|
||||
mdscr_write(mdscr_read() | DBG_MDSCR_SS);
|
||||
enable_debug_monitors(DBG_ACTIVE_EL1);
|
||||
}
|
||||
|
||||
void kernel_disable_single_step(void)
|
||||
{
|
||||
WARN_ON(!irqs_disabled());
|
||||
mdscr_write(mdscr_read() & ~DBG_MDSCR_SS);
|
||||
disable_debug_monitors(DBG_ACTIVE_EL1);
|
||||
}
|
||||
|
||||
int kernel_active_single_step(void)
|
||||
{
|
||||
WARN_ON(!irqs_disabled());
|
||||
return mdscr_read() & DBG_MDSCR_SS;
|
||||
}
|
||||
|
||||
/* ptrace API */
|
||||
void user_enable_single_step(struct task_struct *task)
|
||||
{
|
||||
set_ti_thread_flag(task_thread_info(task), TIF_SINGLESTEP);
|
||||
set_regs_spsr_ss(task_pt_regs(task));
|
||||
}
|
||||
|
||||
void user_disable_single_step(struct task_struct *task)
|
||||
{
|
||||
clear_ti_thread_flag(task_thread_info(task), TIF_SINGLESTEP);
|
||||
}
|
|
@ -0,0 +1,880 @@
|
|||
/*
|
||||
* HW_breakpoint: a unified kernel/user-space hardware breakpoint facility,
|
||||
* using the CPU's debug registers.
|
||||
*
|
||||
* Copyright (C) 2012 ARM Limited
|
||||
* Author: Will Deacon <will.deacon@arm.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "hw-breakpoint: " fmt
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/hw_breakpoint.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/smp.h>
|
||||
|
||||
#include <asm/compat.h>
|
||||
#include <asm/current.h>
|
||||
#include <asm/debug-monitors.h>
|
||||
#include <asm/hw_breakpoint.h>
|
||||
#include <asm/kdebug.h>
|
||||
#include <asm/traps.h>
|
||||
#include <asm/cputype.h>
|
||||
#include <asm/system_misc.h>
|
||||
|
||||
/* Breakpoint currently in use for each BRP. */
|
||||
static DEFINE_PER_CPU(struct perf_event *, bp_on_reg[ARM_MAX_BRP]);
|
||||
|
||||
/* Watchpoint currently in use for each WRP. */
|
||||
static DEFINE_PER_CPU(struct perf_event *, wp_on_reg[ARM_MAX_WRP]);
|
||||
|
||||
/* Currently stepping a per-CPU kernel breakpoint. */
|
||||
static DEFINE_PER_CPU(int, stepping_kernel_bp);
|
||||
|
||||
/* Number of BRP/WRP registers on this CPU. */
|
||||
static int core_num_brps;
|
||||
static int core_num_wrps;
|
||||
|
||||
/* Determine number of BRP registers available. */
|
||||
static int get_num_brps(void)
|
||||
{
|
||||
return ((read_cpuid(ID_AA64DFR0_EL1) >> 12) & 0xf) + 1;
|
||||
}
|
||||
|
||||
/* Determine number of WRP registers available. */
|
||||
static int get_num_wrps(void)
|
||||
{
|
||||
return ((read_cpuid(ID_AA64DFR0_EL1) >> 20) & 0xf) + 1;
|
||||
}
|
||||
|
||||
int hw_breakpoint_slots(int type)
|
||||
{
|
||||
/*
|
||||
* We can be called early, so don't rely on
|
||||
* our static variables being initialised.
|
||||
*/
|
||||
switch (type) {
|
||||
case TYPE_INST:
|
||||
return get_num_brps();
|
||||
case TYPE_DATA:
|
||||
return get_num_wrps();
|
||||
default:
|
||||
pr_warning("unknown slot type: %d\n", type);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define READ_WB_REG_CASE(OFF, N, REG, VAL) \
|
||||
case (OFF + N): \
|
||||
AARCH64_DBG_READ(N, REG, VAL); \
|
||||
break
|
||||
|
||||
#define WRITE_WB_REG_CASE(OFF, N, REG, VAL) \
|
||||
case (OFF + N): \
|
||||
AARCH64_DBG_WRITE(N, REG, VAL); \
|
||||
break
|
||||
|
||||
#define GEN_READ_WB_REG_CASES(OFF, REG, VAL) \
|
||||
READ_WB_REG_CASE(OFF, 0, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 1, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 2, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 3, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 4, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 5, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 6, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 7, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 8, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 9, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 10, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 11, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 12, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 13, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 14, REG, VAL); \
|
||||
READ_WB_REG_CASE(OFF, 15, REG, VAL)
|
||||
|
||||
#define GEN_WRITE_WB_REG_CASES(OFF, REG, VAL) \
|
||||
WRITE_WB_REG_CASE(OFF, 0, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 1, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 2, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 3, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 4, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 5, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 6, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 7, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 8, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 9, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 10, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 11, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 12, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 13, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 14, REG, VAL); \
|
||||
WRITE_WB_REG_CASE(OFF, 15, REG, VAL)
|
||||
|
||||
static u64 read_wb_reg(int reg, int n)
|
||||
{
|
||||
u64 val = 0;
|
||||
|
||||
switch (reg + n) {
|
||||
GEN_READ_WB_REG_CASES(AARCH64_DBG_REG_BVR, AARCH64_DBG_REG_NAME_BVR, val);
|
||||
GEN_READ_WB_REG_CASES(AARCH64_DBG_REG_BCR, AARCH64_DBG_REG_NAME_BCR, val);
|
||||
GEN_READ_WB_REG_CASES(AARCH64_DBG_REG_WVR, AARCH64_DBG_REG_NAME_WVR, val);
|
||||
GEN_READ_WB_REG_CASES(AARCH64_DBG_REG_WCR, AARCH64_DBG_REG_NAME_WCR, val);
|
||||
default:
|
||||
pr_warning("attempt to read from unknown breakpoint register %d\n", n);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void write_wb_reg(int reg, int n, u64 val)
|
||||
{
|
||||
switch (reg + n) {
|
||||
GEN_WRITE_WB_REG_CASES(AARCH64_DBG_REG_BVR, AARCH64_DBG_REG_NAME_BVR, val);
|
||||
GEN_WRITE_WB_REG_CASES(AARCH64_DBG_REG_BCR, AARCH64_DBG_REG_NAME_BCR, val);
|
||||
GEN_WRITE_WB_REG_CASES(AARCH64_DBG_REG_WVR, AARCH64_DBG_REG_NAME_WVR, val);
|
||||
GEN_WRITE_WB_REG_CASES(AARCH64_DBG_REG_WCR, AARCH64_DBG_REG_NAME_WCR, val);
|
||||
default:
|
||||
pr_warning("attempt to write to unknown breakpoint register %d\n", n);
|
||||
}
|
||||
isb();
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a breakpoint privilege level to the corresponding exception
|
||||
* level.
|
||||
*/
|
||||
static enum debug_el debug_exception_level(int privilege)
|
||||
{
|
||||
switch (privilege) {
|
||||
case AARCH64_BREAKPOINT_EL0:
|
||||
return DBG_ACTIVE_EL0;
|
||||
case AARCH64_BREAKPOINT_EL1:
|
||||
return DBG_ACTIVE_EL1;
|
||||
default:
|
||||
pr_warning("invalid breakpoint privilege level %d\n", privilege);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Install a perf counter breakpoint.
|
||||
*/
|
||||
int arch_install_hw_breakpoint(struct perf_event *bp)
|
||||
{
|
||||
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
||||
struct perf_event **slot, **slots;
|
||||
struct debug_info *debug_info = ¤t->thread.debug;
|
||||
int i, max_slots, ctrl_reg, val_reg, reg_enable;
|
||||
u32 ctrl;
|
||||
|
||||
if (info->ctrl.type == ARM_BREAKPOINT_EXECUTE) {
|
||||
/* Breakpoint */
|
||||
ctrl_reg = AARCH64_DBG_REG_BCR;
|
||||
val_reg = AARCH64_DBG_REG_BVR;
|
||||
slots = __get_cpu_var(bp_on_reg);
|
||||
max_slots = core_num_brps;
|
||||
reg_enable = !debug_info->bps_disabled;
|
||||
} else {
|
||||
/* Watchpoint */
|
||||
ctrl_reg = AARCH64_DBG_REG_WCR;
|
||||
val_reg = AARCH64_DBG_REG_WVR;
|
||||
slots = __get_cpu_var(wp_on_reg);
|
||||
max_slots = core_num_wrps;
|
||||
reg_enable = !debug_info->wps_disabled;
|
||||
}
|
||||
|
||||
for (i = 0; i < max_slots; ++i) {
|
||||
slot = &slots[i];
|
||||
|
||||
if (!*slot) {
|
||||
*slot = bp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (WARN_ONCE(i == max_slots, "Can't find any breakpoint slot"))
|
||||
return -ENOSPC;
|
||||
|
||||
/* Ensure debug monitors are enabled at the correct exception level. */
|
||||
enable_debug_monitors(debug_exception_level(info->ctrl.privilege));
|
||||
|
||||
/* Setup the address register. */
|
||||
write_wb_reg(val_reg, i, info->address);
|
||||
|
||||
/* Setup the control register. */
|
||||
ctrl = encode_ctrl_reg(info->ctrl);
|
||||
write_wb_reg(ctrl_reg, i, reg_enable ? ctrl | 0x1 : ctrl & ~0x1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void arch_uninstall_hw_breakpoint(struct perf_event *bp)
|
||||
{
|
||||
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
||||
struct perf_event **slot, **slots;
|
||||
int i, max_slots, base;
|
||||
|
||||
if (info->ctrl.type == ARM_BREAKPOINT_EXECUTE) {
|
||||
/* Breakpoint */
|
||||
base = AARCH64_DBG_REG_BCR;
|
||||
slots = __get_cpu_var(bp_on_reg);
|
||||
max_slots = core_num_brps;
|
||||
} else {
|
||||
/* Watchpoint */
|
||||
base = AARCH64_DBG_REG_WCR;
|
||||
slots = __get_cpu_var(wp_on_reg);
|
||||
max_slots = core_num_wrps;
|
||||
}
|
||||
|
||||
/* Remove the breakpoint. */
|
||||
for (i = 0; i < max_slots; ++i) {
|
||||
slot = &slots[i];
|
||||
|
||||
if (*slot == bp) {
|
||||
*slot = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (WARN_ONCE(i == max_slots, "Can't find any breakpoint slot"))
|
||||
return;
|
||||
|
||||
/* Reset the control register. */
|
||||
write_wb_reg(base, i, 0);
|
||||
|
||||
/* Release the debug monitors for the correct exception level. */
|
||||
disable_debug_monitors(debug_exception_level(info->ctrl.privilege));
|
||||
}
|
||||
|
||||
static int get_hbp_len(u8 hbp_len)
|
||||
{
|
||||
unsigned int len_in_bytes = 0;
|
||||
|
||||
switch (hbp_len) {
|
||||
case ARM_BREAKPOINT_LEN_1:
|
||||
len_in_bytes = 1;
|
||||
break;
|
||||
case ARM_BREAKPOINT_LEN_2:
|
||||
len_in_bytes = 2;
|
||||
break;
|
||||
case ARM_BREAKPOINT_LEN_4:
|
||||
len_in_bytes = 4;
|
||||
break;
|
||||
case ARM_BREAKPOINT_LEN_8:
|
||||
len_in_bytes = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
return len_in_bytes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether bp virtual address is in kernel space.
|
||||
*/
|
||||
int arch_check_bp_in_kernelspace(struct perf_event *bp)
|
||||
{
|
||||
unsigned int len;
|
||||
unsigned long va;
|
||||
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
||||
|
||||
va = info->address;
|
||||
len = get_hbp_len(info->ctrl.len);
|
||||
|
||||
return (va >= TASK_SIZE) && ((va + len - 1) >= TASK_SIZE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract generic type and length encodings from an arch_hw_breakpoint_ctrl.
|
||||
* Hopefully this will disappear when ptrace can bypass the conversion
|
||||
* to generic breakpoint descriptions.
|
||||
*/
|
||||
int arch_bp_generic_fields(struct arch_hw_breakpoint_ctrl ctrl,
|
||||
int *gen_len, int *gen_type)
|
||||
{
|
||||
/* Type */
|
||||
switch (ctrl.type) {
|
||||
case ARM_BREAKPOINT_EXECUTE:
|
||||
*gen_type = HW_BREAKPOINT_X;
|
||||
break;
|
||||
case ARM_BREAKPOINT_LOAD:
|
||||
*gen_type = HW_BREAKPOINT_R;
|
||||
break;
|
||||
case ARM_BREAKPOINT_STORE:
|
||||
*gen_type = HW_BREAKPOINT_W;
|
||||
break;
|
||||
case ARM_BREAKPOINT_LOAD | ARM_BREAKPOINT_STORE:
|
||||
*gen_type = HW_BREAKPOINT_RW;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Len */
|
||||
switch (ctrl.len) {
|
||||
case ARM_BREAKPOINT_LEN_1:
|
||||
*gen_len = HW_BREAKPOINT_LEN_1;
|
||||
break;
|
||||
case ARM_BREAKPOINT_LEN_2:
|
||||
*gen_len = HW_BREAKPOINT_LEN_2;
|
||||
break;
|
||||
case ARM_BREAKPOINT_LEN_4:
|
||||
*gen_len = HW_BREAKPOINT_LEN_4;
|
||||
break;
|
||||
case ARM_BREAKPOINT_LEN_8:
|
||||
*gen_len = HW_BREAKPOINT_LEN_8;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct an arch_hw_breakpoint from a perf_event.
|
||||
*/
|
||||
static int arch_build_bp_info(struct perf_event *bp)
|
||||
{
|
||||
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
||||
|
||||
/* Type */
|
||||
switch (bp->attr.bp_type) {
|
||||
case HW_BREAKPOINT_X:
|
||||
info->ctrl.type = ARM_BREAKPOINT_EXECUTE;
|
||||
break;
|
||||
case HW_BREAKPOINT_R:
|
||||
info->ctrl.type = ARM_BREAKPOINT_LOAD;
|
||||
break;
|
||||
case HW_BREAKPOINT_W:
|
||||
info->ctrl.type = ARM_BREAKPOINT_STORE;
|
||||
break;
|
||||
case HW_BREAKPOINT_RW:
|
||||
info->ctrl.type = ARM_BREAKPOINT_LOAD | ARM_BREAKPOINT_STORE;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Len */
|
||||
switch (bp->attr.bp_len) {
|
||||
case HW_BREAKPOINT_LEN_1:
|
||||
info->ctrl.len = ARM_BREAKPOINT_LEN_1;
|
||||
break;
|
||||
case HW_BREAKPOINT_LEN_2:
|
||||
info->ctrl.len = ARM_BREAKPOINT_LEN_2;
|
||||
break;
|
||||
case HW_BREAKPOINT_LEN_4:
|
||||
info->ctrl.len = ARM_BREAKPOINT_LEN_4;
|
||||
break;
|
||||
case HW_BREAKPOINT_LEN_8:
|
||||
info->ctrl.len = ARM_BREAKPOINT_LEN_8;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* On AArch64, we only permit breakpoints of length 4, whereas
|
||||
* AArch32 also requires breakpoints of length 2 for Thumb.
|
||||
* Watchpoints can be of length 1, 2, 4 or 8 bytes.
|
||||
*/
|
||||
if (info->ctrl.type == ARM_BREAKPOINT_EXECUTE) {
|
||||
if (is_compat_task()) {
|
||||
if (info->ctrl.len != ARM_BREAKPOINT_LEN_2 &&
|
||||
info->ctrl.len != ARM_BREAKPOINT_LEN_4)
|
||||
return -EINVAL;
|
||||
} else if (info->ctrl.len != ARM_BREAKPOINT_LEN_4) {
|
||||
/*
|
||||
* FIXME: Some tools (I'm looking at you perf) assume
|
||||
* that breakpoints should be sizeof(long). This
|
||||
* is nonsense. For now, we fix up the parameter
|
||||
* but we should probably return -EINVAL instead.
|
||||
*/
|
||||
info->ctrl.len = ARM_BREAKPOINT_LEN_4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Address */
|
||||
info->address = bp->attr.bp_addr;
|
||||
|
||||
/*
|
||||
* Privilege
|
||||
* Note that we disallow combined EL0/EL1 breakpoints because
|
||||
* that would complicate the stepping code.
|
||||
*/
|
||||
if (arch_check_bp_in_kernelspace(bp))
|
||||
info->ctrl.privilege = AARCH64_BREAKPOINT_EL1;
|
||||
else
|
||||
info->ctrl.privilege = AARCH64_BREAKPOINT_EL0;
|
||||
|
||||
/* Enabled? */
|
||||
info->ctrl.enabled = !bp->attr.disabled;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate the arch-specific HW Breakpoint register settings.
|
||||
*/
|
||||
int arch_validate_hwbkpt_settings(struct perf_event *bp)
|
||||
{
|
||||
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
||||
int ret;
|
||||
u64 alignment_mask, offset;
|
||||
|
||||
/* Build the arch_hw_breakpoint. */
|
||||
ret = arch_build_bp_info(bp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Check address alignment.
|
||||
* We don't do any clever alignment correction for watchpoints
|
||||
* because using 64-bit unaligned addresses is deprecated for
|
||||
* AArch64.
|
||||
*
|
||||
* AArch32 tasks expect some simple alignment fixups, so emulate
|
||||
* that here.
|
||||
*/
|
||||
if (is_compat_task()) {
|
||||
if (info->ctrl.len == ARM_BREAKPOINT_LEN_8)
|
||||
alignment_mask = 0x7;
|
||||
else
|
||||
alignment_mask = 0x3;
|
||||
offset = info->address & alignment_mask;
|
||||
switch (offset) {
|
||||
case 0:
|
||||
/* Aligned */
|
||||
break;
|
||||
case 1:
|
||||
/* Allow single byte watchpoint. */
|
||||
if (info->ctrl.len == ARM_BREAKPOINT_LEN_1)
|
||||
break;
|
||||
case 2:
|
||||
/* Allow halfword watchpoints and breakpoints. */
|
||||
if (info->ctrl.len == ARM_BREAKPOINT_LEN_2)
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
info->address &= ~alignment_mask;
|
||||
info->ctrl.len <<= offset;
|
||||
} else {
|
||||
if (info->ctrl.type == ARM_BREAKPOINT_EXECUTE)
|
||||
alignment_mask = 0x3;
|
||||
else
|
||||
alignment_mask = 0x7;
|
||||
if (info->address & alignment_mask)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disallow per-task kernel breakpoints since these would
|
||||
* complicate the stepping code.
|
||||
*/
|
||||
if (info->ctrl.privilege == AARCH64_BREAKPOINT_EL1 && bp->hw.bp_target)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable/disable all of the breakpoints active at the specified
|
||||
* exception level at the register level.
|
||||
* This is used when single-stepping after a breakpoint exception.
|
||||
*/
|
||||
static void toggle_bp_registers(int reg, enum debug_el el, int enable)
|
||||
{
|
||||
int i, max_slots, privilege;
|
||||
u32 ctrl;
|
||||
struct perf_event **slots;
|
||||
|
||||
switch (reg) {
|
||||
case AARCH64_DBG_REG_BCR:
|
||||
slots = __get_cpu_var(bp_on_reg);
|
||||
max_slots = core_num_brps;
|
||||
break;
|
||||
case AARCH64_DBG_REG_WCR:
|
||||
slots = __get_cpu_var(wp_on_reg);
|
||||
max_slots = core_num_wrps;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < max_slots; ++i) {
|
||||
if (!slots[i])
|
||||
continue;
|
||||
|
||||
privilege = counter_arch_bp(slots[i])->ctrl.privilege;
|
||||
if (debug_exception_level(privilege) != el)
|
||||
continue;
|
||||
|
||||
ctrl = read_wb_reg(reg, i);
|
||||
if (enable)
|
||||
ctrl |= 0x1;
|
||||
else
|
||||
ctrl &= ~0x1;
|
||||
write_wb_reg(reg, i, ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Debug exception handlers.
|
||||
*/
|
||||
static int breakpoint_handler(unsigned long unused, unsigned int esr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
int i, step = 0, *kernel_step;
|
||||
u32 ctrl_reg;
|
||||
u64 addr, val;
|
||||
struct perf_event *bp, **slots;
|
||||
struct debug_info *debug_info;
|
||||
struct arch_hw_breakpoint_ctrl ctrl;
|
||||
|
||||
slots = (struct perf_event **)__get_cpu_var(bp_on_reg);
|
||||
addr = instruction_pointer(regs);
|
||||
debug_info = ¤t->thread.debug;
|
||||
|
||||
for (i = 0; i < core_num_brps; ++i) {
|
||||
rcu_read_lock();
|
||||
|
||||
bp = slots[i];
|
||||
|
||||
if (bp == NULL)
|
||||
goto unlock;
|
||||
|
||||
/* Check if the breakpoint value matches. */
|
||||
val = read_wb_reg(AARCH64_DBG_REG_BVR, i);
|
||||
if (val != (addr & ~0x3))
|
||||
goto unlock;
|
||||
|
||||
/* Possible match, check the byte address select to confirm. */
|
||||
ctrl_reg = read_wb_reg(AARCH64_DBG_REG_BCR, i);
|
||||
decode_ctrl_reg(ctrl_reg, &ctrl);
|
||||
if (!((1 << (addr & 0x3)) & ctrl.len))
|
||||
goto unlock;
|
||||
|
||||
counter_arch_bp(bp)->trigger = addr;
|
||||
perf_bp_event(bp, regs);
|
||||
|
||||
/* Do we need to handle the stepping? */
|
||||
if (!bp->overflow_handler)
|
||||
step = 1;
|
||||
unlock:
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
if (!step)
|
||||
return 0;
|
||||
|
||||
if (user_mode(regs)) {
|
||||
debug_info->bps_disabled = 1;
|
||||
toggle_bp_registers(AARCH64_DBG_REG_BCR, DBG_ACTIVE_EL0, 0);
|
||||
|
||||
/* If we're already stepping a watchpoint, just return. */
|
||||
if (debug_info->wps_disabled)
|
||||
return 0;
|
||||
|
||||
if (test_thread_flag(TIF_SINGLESTEP))
|
||||
debug_info->suspended_step = 1;
|
||||
else
|
||||
user_enable_single_step(current);
|
||||
} else {
|
||||
toggle_bp_registers(AARCH64_DBG_REG_BCR, DBG_ACTIVE_EL1, 0);
|
||||
kernel_step = &__get_cpu_var(stepping_kernel_bp);
|
||||
|
||||
if (*kernel_step != ARM_KERNEL_STEP_NONE)
|
||||
return 0;
|
||||
|
||||
if (kernel_active_single_step()) {
|
||||
*kernel_step = ARM_KERNEL_STEP_SUSPEND;
|
||||
} else {
|
||||
*kernel_step = ARM_KERNEL_STEP_ACTIVE;
|
||||
kernel_enable_single_step(regs);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int watchpoint_handler(unsigned long addr, unsigned int esr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
int i, step = 0, *kernel_step, access;
|
||||
u32 ctrl_reg;
|
||||
u64 val, alignment_mask;
|
||||
struct perf_event *wp, **slots;
|
||||
struct debug_info *debug_info;
|
||||
struct arch_hw_breakpoint *info;
|
||||
struct arch_hw_breakpoint_ctrl ctrl;
|
||||
|
||||
slots = (struct perf_event **)__get_cpu_var(wp_on_reg);
|
||||
debug_info = ¤t->thread.debug;
|
||||
|
||||
for (i = 0; i < core_num_wrps; ++i) {
|
||||
rcu_read_lock();
|
||||
|
||||
wp = slots[i];
|
||||
|
||||
if (wp == NULL)
|
||||
goto unlock;
|
||||
|
||||
info = counter_arch_bp(wp);
|
||||
/* AArch32 watchpoints are either 4 or 8 bytes aligned. */
|
||||
if (is_compat_task()) {
|
||||
if (info->ctrl.len == ARM_BREAKPOINT_LEN_8)
|
||||
alignment_mask = 0x7;
|
||||
else
|
||||
alignment_mask = 0x3;
|
||||
} else {
|
||||
alignment_mask = 0x7;
|
||||
}
|
||||
|
||||
/* Check if the watchpoint value matches. */
|
||||
val = read_wb_reg(AARCH64_DBG_REG_WVR, i);
|
||||
if (val != (addr & ~alignment_mask))
|
||||
goto unlock;
|
||||
|
||||
/* Possible match, check the byte address select to confirm. */
|
||||
ctrl_reg = read_wb_reg(AARCH64_DBG_REG_WCR, i);
|
||||
decode_ctrl_reg(ctrl_reg, &ctrl);
|
||||
if (!((1 << (addr & alignment_mask)) & ctrl.len))
|
||||
goto unlock;
|
||||
|
||||
/*
|
||||
* Check that the access type matches.
|
||||
* 0 => load, otherwise => store
|
||||
*/
|
||||
access = (esr & AARCH64_ESR_ACCESS_MASK) ? HW_BREAKPOINT_W :
|
||||
HW_BREAKPOINT_R;
|
||||
if (!(access & hw_breakpoint_type(wp)))
|
||||
goto unlock;
|
||||
|
||||
info->trigger = addr;
|
||||
perf_bp_event(wp, regs);
|
||||
|
||||
/* Do we need to handle the stepping? */
|
||||
if (!wp->overflow_handler)
|
||||
step = 1;
|
||||
|
||||
unlock:
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
if (!step)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* We always disable EL0 watchpoints because the kernel can
|
||||
* cause these to fire via an unprivileged access.
|
||||
*/
|
||||
toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL0, 0);
|
||||
|
||||
if (user_mode(regs)) {
|
||||
debug_info->wps_disabled = 1;
|
||||
|
||||
/* If we're already stepping a breakpoint, just return. */
|
||||
if (debug_info->bps_disabled)
|
||||
return 0;
|
||||
|
||||
if (test_thread_flag(TIF_SINGLESTEP))
|
||||
debug_info->suspended_step = 1;
|
||||
else
|
||||
user_enable_single_step(current);
|
||||
} else {
|
||||
toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL1, 0);
|
||||
kernel_step = &__get_cpu_var(stepping_kernel_bp);
|
||||
|
||||
if (*kernel_step != ARM_KERNEL_STEP_NONE)
|
||||
return 0;
|
||||
|
||||
if (kernel_active_single_step()) {
|
||||
*kernel_step = ARM_KERNEL_STEP_SUSPEND;
|
||||
} else {
|
||||
*kernel_step = ARM_KERNEL_STEP_ACTIVE;
|
||||
kernel_enable_single_step(regs);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle single-step exception.
|
||||
*/
|
||||
int reinstall_suspended_bps(struct pt_regs *regs)
|
||||
{
|
||||
struct debug_info *debug_info = ¤t->thread.debug;
|
||||
int handled_exception = 0, *kernel_step;
|
||||
|
||||
kernel_step = &__get_cpu_var(stepping_kernel_bp);
|
||||
|
||||
/*
|
||||
* Called from single-step exception handler.
|
||||
* Return 0 if execution can resume, 1 if a SIGTRAP should be
|
||||
* reported.
|
||||
*/
|
||||
if (user_mode(regs)) {
|
||||
if (debug_info->bps_disabled) {
|
||||
debug_info->bps_disabled = 0;
|
||||
toggle_bp_registers(AARCH64_DBG_REG_BCR, DBG_ACTIVE_EL0, 1);
|
||||
handled_exception = 1;
|
||||
}
|
||||
|
||||
if (debug_info->wps_disabled) {
|
||||
debug_info->wps_disabled = 0;
|
||||
toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL0, 1);
|
||||
handled_exception = 1;
|
||||
}
|
||||
|
||||
if (handled_exception) {
|
||||
if (debug_info->suspended_step) {
|
||||
debug_info->suspended_step = 0;
|
||||
/* Allow exception handling to fall-through. */
|
||||
handled_exception = 0;
|
||||
} else {
|
||||
user_disable_single_step(current);
|
||||
}
|
||||
}
|
||||
} else if (*kernel_step != ARM_KERNEL_STEP_NONE) {
|
||||
toggle_bp_registers(AARCH64_DBG_REG_BCR, DBG_ACTIVE_EL1, 1);
|
||||
toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL1, 1);
|
||||
|
||||
if (!debug_info->wps_disabled)
|
||||
toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL0, 1);
|
||||
|
||||
if (*kernel_step != ARM_KERNEL_STEP_SUSPEND) {
|
||||
kernel_disable_single_step();
|
||||
handled_exception = 1;
|
||||
} else {
|
||||
handled_exception = 0;
|
||||
}
|
||||
|
||||
*kernel_step = ARM_KERNEL_STEP_NONE;
|
||||
}
|
||||
|
||||
return !handled_exception;
|
||||
}
|
||||
|
||||
/*
|
||||
* Context-switcher for restoring suspended breakpoints.
|
||||
*/
|
||||
void hw_breakpoint_thread_switch(struct task_struct *next)
|
||||
{
|
||||
/*
|
||||
* current next
|
||||
* disabled: 0 0 => The usual case, NOTIFY_DONE
|
||||
* 0 1 => Disable the registers
|
||||
* 1 0 => Enable the registers
|
||||
* 1 1 => NOTIFY_DONE. per-task bps will
|
||||
* get taken care of by perf.
|
||||
*/
|
||||
|
||||
struct debug_info *current_debug_info, *next_debug_info;
|
||||
|
||||
current_debug_info = ¤t->thread.debug;
|
||||
next_debug_info = &next->thread.debug;
|
||||
|
||||
/* Update breakpoints. */
|
||||
if (current_debug_info->bps_disabled != next_debug_info->bps_disabled)
|
||||
toggle_bp_registers(AARCH64_DBG_REG_BCR,
|
||||
DBG_ACTIVE_EL0,
|
||||
!next_debug_info->bps_disabled);
|
||||
|
||||
/* Update watchpoints. */
|
||||
if (current_debug_info->wps_disabled != next_debug_info->wps_disabled)
|
||||
toggle_bp_registers(AARCH64_DBG_REG_WCR,
|
||||
DBG_ACTIVE_EL0,
|
||||
!next_debug_info->wps_disabled);
|
||||
}
|
||||
|
||||
/*
|
||||
* CPU initialisation.
|
||||
*/
|
||||
static void reset_ctrl_regs(void *unused)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < core_num_brps; ++i) {
|
||||
write_wb_reg(AARCH64_DBG_REG_BCR, i, 0UL);
|
||||
write_wb_reg(AARCH64_DBG_REG_BVR, i, 0UL);
|
||||
}
|
||||
|
||||
for (i = 0; i < core_num_wrps; ++i) {
|
||||
write_wb_reg(AARCH64_DBG_REG_WCR, i, 0UL);
|
||||
write_wb_reg(AARCH64_DBG_REG_WVR, i, 0UL);
|
||||
}
|
||||
}
|
||||
|
||||
static int __cpuinit hw_breakpoint_reset_notify(struct notifier_block *self,
|
||||
unsigned long action,
|
||||
void *hcpu)
|
||||
{
|
||||
int cpu = (long)hcpu;
|
||||
if (action == CPU_ONLINE)
|
||||
smp_call_function_single(cpu, reset_ctrl_regs, NULL, 1);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block __cpuinitdata hw_breakpoint_reset_nb = {
|
||||
.notifier_call = hw_breakpoint_reset_notify,
|
||||
};
|
||||
|
||||
/*
|
||||
* One-time initialisation.
|
||||
*/
|
||||
static int __init arch_hw_breakpoint_init(void)
|
||||
{
|
||||
core_num_brps = get_num_brps();
|
||||
core_num_wrps = get_num_wrps();
|
||||
|
||||
pr_info("found %d breakpoint and %d watchpoint registers.\n",
|
||||
core_num_brps, core_num_wrps);
|
||||
|
||||
/*
|
||||
* Reset the breakpoint resources. We assume that a halting
|
||||
* debugger will leave the world in a nice state for us.
|
||||
*/
|
||||
smp_call_function(reset_ctrl_regs, NULL, 1);
|
||||
reset_ctrl_regs(NULL);
|
||||
|
||||
/* Register debug fault handlers. */
|
||||
hook_debug_fault_code(DBG_ESR_EVT_HWBP, breakpoint_handler, SIGTRAP,
|
||||
TRAP_HWBKPT, "hw-breakpoint handler");
|
||||
hook_debug_fault_code(DBG_ESR_EVT_HWWP, watchpoint_handler, SIGTRAP,
|
||||
TRAP_HWBKPT, "hw-watchpoint handler");
|
||||
|
||||
/* Register hotplug notifier. */
|
||||
register_cpu_notifier(&hw_breakpoint_reset_nb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
arch_initcall(arch_hw_breakpoint_init);
|
||||
|
||||
void hw_breakpoint_pmu_read(struct perf_event *bp)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Dummy function to register with die_notifier.
|
||||
*/
|
||||
int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
|
||||
unsigned long val, void *data)
|
||||
{
|
||||
return NOTIFY_DONE;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -388,6 +388,9 @@ typedef struct elf64_shdr {
|
|||
#define NT_S390_LAST_BREAK 0x306 /* s390 breaking event address */
|
||||
#define NT_S390_SYSTEM_CALL 0x307 /* s390 system call restart data */
|
||||
#define NT_ARM_VFP 0x400 /* ARM VFP/NEON registers */
|
||||
#define NT_ARM_TLS 0x401 /* ARM TLS register */
|
||||
#define NT_ARM_HW_BREAK 0x402 /* ARM hardware breakpoint registers */
|
||||
#define NT_ARM_HW_WATCH 0x403 /* ARM hardware watchpoint registers */
|
||||
|
||||
|
||||
/* Note header in a PT_NOTE section */
|
||||
|
|
Loading…
Reference in New Issue