ARM64 hw watchpoint、breakpoint注冊與注銷
write_wb_reg()里的switch macro展開
4.19\arch\arm64\kernel\Hw_breakpoint.c
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(); } above switch expanding as below: switch (reg + n) { /* wvr(breakpoint value register) */ case (0): \ do {\ write_sysreg(val, dbgbvr0_el1);\ } while (0); \ break; case (1): \ do {\ write_sysreg(val, dbgbvr1_el1);\ } while (0); \ break; case (15): \ do {\ write_sysreg(val, dbgbvr15_el1);\ } while (0); \ break; /* wvr(breakpoint ctrl register) */ case (16 + 0): \ do {\ write_sysreg(VAL, dbgbcr0_el1);\ } while (0); \ break case (16 + 1): \ do {\ write_sysreg(VAL, dbgbcr1_el1);\ } while (0); \ break case (16 + 15): \ do {\ write_sysreg(VAL, dbgbcr15_el1);\ } while (0); \ break /* wvr(watchpoint value register) */ case (32 + 0): \ do {\ write_sysreg(VAL, dbgwvr0_el1);\ } while (0); \ break case (32 + 1): \ do {\ write_sysreg(VAL, dbgwvr1_el1);\ } while (0); \ break case (32 + 15): \ do {\ write_sysreg(VAL, dbgwvr15_el1);\ } while (0); \ break /* wvr(watchpoint ctrl register) */ case (48 + 0): \ do {\ write_sysreg(VAL, dbgwcr0_el1);\ } while (0); \ break case (48 + 1): \ do {\ write_sysreg(VAL, dbgwcr1_el1);\ } while (0); \ break case (48 + 15): \ do {\ write_sysreg(VAL, dbgwcr15_el1);\ } while (0); \ break }
bvr/bcr/wvr/wcr寄存器分別為breakpoint value register/breakpoint ctrl register/watchpoint value register/watchpoint ctrl register
這些寄存器描述見下面鏈接:
https://developer.arm.com/documentation/ddi0500/e/debug/memory-mapped-register-summary
注冊watchpoint callstack
設置watchpoint、breakpoint需要設置CPU寄存器,設置寄存器是通過上述write_wb_reg()來設置的,它的示例設置callstack如下:
[ 28.809660] CPU: 1 PID: 34 Comm: kworker/1:1 Tainted: P O 4.19.116+ #49 [ 28.817581] Hardware name: test_mach (DT) [ 28.821261] Workqueue: events watch_point_delayed_work_func [ 28.826841] Call trace: [ 28.829293] dump_backtrace+0x0/0x4 [ 28.832786] dump_stack+0xf4/0x134 [ 28.836192] write_wb_reg+0x3c/0x284 [ 28.839769] hw_breakpoint_control+0x18c/0x264 [ 28.844216] hw_breakpoint_add+0x80/0x8c [ 28.848143] event_sched_in+0x44c/0x718 [ 28.851981] group_sched_in+0x6c/0x218 [ 28.855732] pinned_sched_in+0x158/0x264 [ 28.859656] visit_groups_merge+0x140/0x1fc [ 28.863842] ctx_sched_in+0x1a4/0x1ec [ 28.867507] ctx_resched+0xdc/0x1e4 [ 28.870998] __perf_event_enable+0x1d0/0x2c4 [ 28.875271] event_function.9346+0x128/0x224 [ 28.879543] remote_function+0x74/0xb4 [ 28.883297] generic_exec_single+0x7c/0x1a4 [ 28.887483] smp_call_function_single+0xc0/0x1e8 [ 28.892104] event_function_call+0x84/0x274 [ 28.896289] perf_event_enable+0xec/0x158 [ 28.900301] _register_wp_bp+0x408/0x4b8 [ 28.904228] watch_point_delayed_work_func+0x68/0xcc [ 28.909197] process_one_work+0x3c0/0x670 [ 28.913210] worker_thread+0x32c/0x6d0 [ 28.916962] kthread+0x130/0x140 [ 28.920194] ret_from_fork+0x10/0x18
arm64 CPU支持的watchpoint、breakpoint max num
4.19\arch\arm64\include\asm\Hw_breakpoint.h
/* Determine number of BRP registers available. */ static inline int get_num_brps(void) { u64 dfr0 = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1); return 1 + cpuid_feature_extract_unsigned_field(dfr0, ID_AA64DFR0_BRPS_SHIFT); } /* Determine number of WRP registers available. */ static inline int get_num_wrps(void) { u64 dfr0 = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1); return 1 + cpuid_feature_extract_unsigned_field(dfr0, ID_AA64DFR0_WRPS_SHIFT); }
確定CPU支持的watchpoint、breakpoint max num:
arch/arm64/kernel/hw_breakpoint.c
static int __init arch_hw_breakpoint_init(void) { int ret; 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);
比如我這邊平台CPU支持的max watchpoint、breakpoint num分別為4、6:
[ 1.399866] hw-breakpoint: found 6 breakpoint and 4 watchpoint registers.
以watchpoint為例,如果當前已經注冊了4個,如果再繼續注冊,則會注冊fail,返回-ENOSPC(-28)
arch/arm64/kernel/hw_breakpoint.c
static int hw_breakpoint_control(struct perf_event *bp, enum hw_breakpoint_ops ops) { struct arch_hw_breakpoint *info = counter_arch_bp(bp); struct perf_event **slots; struct debug_info *debug_info = ¤t->thread.debug; int i, max_slots, ctrl_reg, val_reg, reg_enable; enum dbg_active_el dbg_el = debug_exception_level(info->ctrl.privilege); u32 ctrl; if (info->ctrl.type == ARM_BREAKPOINT_EXECUTE) { /* Breakpoint */ ctrl_reg = AARCH64_DBG_REG_BCR; val_reg = AARCH64_DBG_REG_BVR; slots = this_cpu_ptr(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 = this_cpu_ptr(wp_on_reg); max_slots = core_num_wrps; reg_enable = !debug_info->wps_disabled; } i = hw_breakpoint_slot_setup(slots, max_slots, bp, ops);
static int hw_breakpoint_slot_setup(struct perf_event **slots, int max_slots, struct perf_event *bp, enum hw_breakpoint_ops ops) { int i; struct perf_event **slot; for (i = 0; i < max_slots; ++i) { slot = &slots[i]; switch (ops) { case HW_BREAKPOINT_INSTALL: if (!*slot) { *slot = bp; return i; } break; case HW_BREAKPOINT_UNINSTALL: if (*slot == bp) { *slot = NULL; return i; } break; case HW_BREAKPOINT_RESTORE: if (*slot == bp) return i; break; default: pr_warn_once("Unhandled hw breakpoint ops %d\n", ops); return -EINVAL; } } return -ENOSPC; }
注銷watch point callstack
注銷一個watchpoint callstack如下,call到hw_breakpoint_control()將ctrl reg設置為0,ops為HW_BREAKPOINT_UNINSTALL:
[ 49.043918] hw-breakpoint: ops: 1, reg_enable: 1. [ 49.043925] CPU: 0 PID: 3237 Comm: mali-cmar-backe Tainted: P O 4.19.116+ #19 [ 49.043927] Hardware name: xxx_mach (DT) [ 49.043931] Call trace: [ 49.043942] dump_backtrace+0x0/0x4 [ 49.043949] dump_stack+0xf4/0x134 [ 49.043957] hw_breakpoint_control+0x184/0x29c [ 49.043963] hw_breakpoint_del+0x20/0x2c [ 49.043968] event_sched_out+0xe0/0x344 [ 49.043973] __perf_remove_from_context+0xfc/0x16c [ 49.043977] event_function.9234+0x128/0x224 [ 49.043981] remote_function+0x74/0xb4 [ 49.043988] generic_exec_single+0x7c/0x1a4 [ 49.043992] smp_call_function_single+0xc0/0x1e8 [ 49.043996] event_function_call+0x84/0x274 [ 49.044000] perf_event_release_kernel+0x1b8/0x794 [ 49.044004] unregister_wide_hw_breakpoint+0x90/0xf4 [ 49.044010] _unregister_wp_bp+0x10c/0x198 [ 49.044016] exit_files+0x1a0/0x1d8 [ 49.044020] do_exit+0x8ec/0x1420 [ 49.044024] do_group_exit+0x0/0x160 [ 49.044027] __se_sys_exit+0x0/0x20 [ 49.044031] el0_svc_common+0xb8/0x1b8 [ 49.044035] el0_svc_handler+0x74/0x90 [ 49.044040] el0_svc+0x8/0x340
4.19\arch\arm64\kernel\Hw_breakpoint.c
static int hw_breakpoint_control(struct perf_event *bp,
enum hw_breakpoint_ops ops)
case HW_BREAKPOINT_UNINSTALL: /* Reset the control register. */ write_wb_reg(ctrl_reg, i, 0); /* * Release the debug monitors for the correct exception * level. */ disable_debug_monitors(dbg_el); break;