From 46747d15080a93cc82cac563c1b7b8ffef164bb5 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 29 Sep 2014 18:48:46 +0100 Subject: [PATCH] target-arm: Implement setting guest breakpoints This patch adds support for setting guest breakpoints based on values the guest writes to the DBGBVR and DBGBCR registers. (It doesn't include the code to handle when these breakpoints fire, so has no guest-visible effect.) Signed-off-by: Peter Maydell Message-id: 1410523465-13400-2-git-send-email-peter.maydell@linaro.org --- target-arm/cpu.c | 1 + target-arm/cpu.h | 1 + target-arm/helper.c | 126 ++++++++++++++++++++++++++++++++++++++++++++++++- target-arm/internals.h | 9 ++++ target-arm/machine.c | 1 + 5 files changed, 136 insertions(+), 2 deletions(-) diff --git a/target-arm/cpu.c b/target-arm/cpu.c index 407f977..6ab0e03 100644 --- a/target-arm/cpu.c +++ b/target-arm/cpu.c @@ -185,6 +185,7 @@ static void arm_cpu_reset(CPUState *s) } #endif + hw_breakpoint_update_all(cpu); hw_watchpoint_update_all(cpu); } diff --git a/target-arm/cpu.h b/target-arm/cpu.h index d1e1ccb..fa6ae0a 100644 --- a/target-arm/cpu.h +++ b/target-arm/cpu.h @@ -323,6 +323,7 @@ typedef struct CPUARMState { int eabi; #endif + struct CPUBreakpoint *cpu_breakpoint[16]; struct CPUWatchpoint *cpu_watchpoint[16]; CPU_COMMON diff --git a/target-arm/helper.c b/target-arm/helper.c index ece9673..d246d36 100644 --- a/target-arm/helper.c +++ b/target-arm/helper.c @@ -2492,6 +2492,124 @@ static void dbgwcr_write(CPUARMState *env, const ARMCPRegInfo *ri, hw_watchpoint_update(cpu, i); } +void hw_breakpoint_update(ARMCPU *cpu, int n) +{ + CPUARMState *env = &cpu->env; + uint64_t bvr = env->cp15.dbgbvr[n]; + uint64_t bcr = env->cp15.dbgbcr[n]; + vaddr addr; + int bt; + int flags = BP_CPU; + + if (env->cpu_breakpoint[n]) { + cpu_breakpoint_remove_by_ref(CPU(cpu), env->cpu_breakpoint[n]); + env->cpu_breakpoint[n] = NULL; + } + + if (!extract64(bcr, 0, 1)) { + /* E bit clear : watchpoint disabled */ + return; + } + + bt = extract64(bcr, 20, 4); + + switch (bt) { + case 4: /* unlinked address mismatch (reserved if AArch64) */ + case 5: /* linked address mismatch (reserved if AArch64) */ + qemu_log_mask(LOG_UNIMP, + "arm: address mismatch breakpoint types not implemented"); + return; + case 0: /* unlinked address match */ + case 1: /* linked address match */ + { + /* Bits [63:49] are hardwired to the value of bit [48]; that is, + * we behave as if the register was sign extended. Bits [1:0] are + * RES0. The BAS field is used to allow setting breakpoints on 16 + * bit wide instructions; it is CONSTRAINED UNPREDICTABLE whether + * a bp will fire if the addresses covered by the bp and the addresses + * covered by the insn overlap but the insn doesn't start at the + * start of the bp address range. We choose to require the insn and + * the bp to have the same address. The constraints on writing to + * BAS enforced in dbgbcr_write mean we have only four cases: + * 0b0000 => no breakpoint + * 0b0011 => breakpoint on addr + * 0b1100 => breakpoint on addr + 2 + * 0b1111 => breakpoint on addr + * See also figure D2-3 in the v8 ARM ARM (DDI0487A.c). + */ + int bas = extract64(bcr, 5, 4); + addr = sextract64(bvr, 0, 49) & ~3ULL; + if (bas == 0) { + return; + } + if (bas == 0xc) { + addr += 2; + } + break; + } + case 2: /* unlinked context ID match */ + case 8: /* unlinked VMID match (reserved if no EL2) */ + case 10: /* unlinked context ID and VMID match (reserved if no EL2) */ + qemu_log_mask(LOG_UNIMP, + "arm: unlinked context breakpoint types not implemented"); + return; + case 9: /* linked VMID match (reserved if no EL2) */ + case 11: /* linked context ID and VMID match (reserved if no EL2) */ + case 3: /* linked context ID match */ + default: + /* We must generate no events for Linked context matches (unless + * they are linked to by some other bp/wp, which is handled in + * updates for the linking bp/wp). We choose to also generate no events + * for reserved values. + */ + return; + } + + cpu_breakpoint_insert(CPU(cpu), addr, flags, &env->cpu_breakpoint[n]); +} + +void hw_breakpoint_update_all(ARMCPU *cpu) +{ + int i; + CPUARMState *env = &cpu->env; + + /* Completely clear out existing QEMU breakpoints and our array, to + * avoid possible stale entries following migration load. + */ + cpu_breakpoint_remove_all(CPU(cpu), BP_CPU); + memset(env->cpu_breakpoint, 0, sizeof(env->cpu_breakpoint)); + + for (i = 0; i < ARRAY_SIZE(cpu->env.cpu_breakpoint); i++) { + hw_breakpoint_update(cpu, i); + } +} + +static void dbgbvr_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + ARMCPU *cpu = arm_env_get_cpu(env); + int i = ri->crm; + + raw_write(env, ri, value); + hw_breakpoint_update(cpu, i); +} + +static void dbgbcr_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + ARMCPU *cpu = arm_env_get_cpu(env); + int i = ri->crm; + + /* BAS[3] is a read-only copy of BAS[2], and BAS[1] a read-only + * copy of BAS[0]. + */ + value = deposit64(value, 6, 1, extract64(value, 5, 1)); + value = deposit64(value, 8, 1, extract64(value, 7, 1)); + + raw_write(env, ri, value); + hw_breakpoint_update(cpu, i); +} + static void define_debug_regs(ARMCPU *cpu) { /* Define v7 and v8 architectural debug registers. @@ -2533,11 +2651,15 @@ static void define_debug_regs(ARMCPU *cpu) { .name = "DBGBVR", .state = ARM_CP_STATE_BOTH, .cp = 14, .opc0 = 2, .opc1 = 0, .crn = 0, .crm = i, .opc2 = 4, .access = PL1_RW, - .fieldoffset = offsetof(CPUARMState, cp15.dbgbvr[i]) }, + .fieldoffset = offsetof(CPUARMState, cp15.dbgbvr[i]), + .writefn = dbgbvr_write, .raw_writefn = raw_write + }, { .name = "DBGBCR", .state = ARM_CP_STATE_BOTH, .cp = 14, .opc0 = 2, .opc1 = 0, .crn = 0, .crm = i, .opc2 = 5, .access = PL1_RW, - .fieldoffset = offsetof(CPUARMState, cp15.dbgbcr[i]) }, + .fieldoffset = offsetof(CPUARMState, cp15.dbgbcr[i]), + .writefn = dbgbcr_write, .raw_writefn = raw_write + }, REGINFO_SENTINEL }; define_arm_cp_regs(cpu, dbgregs); diff --git a/target-arm/internals.h b/target-arm/internals.h index 64751a0..b7e4822 100644 --- a/target-arm/internals.h +++ b/target-arm/internals.h @@ -322,6 +322,15 @@ void hw_watchpoint_update(ARMCPU *cpu, int n); * suitable for use after migration or on reset. */ void hw_watchpoint_update_all(ARMCPU *cpu); +/* Update a QEMU breakpoint based on the information the guest has set in the + * DBGBCR_EL1 and DBGBVR_EL1 registers. + */ +void hw_breakpoint_update(ARMCPU *cpu, int n); +/* Update the QEMU breakpoints for every guest breakpoint. This does a + * complete delete-and-reinstate of the QEMU breakpoint list and so is + * suitable for use after migration or on reset. + */ +void hw_breakpoint_update_all(ARMCPU *cpu); /* Callback function for when a watchpoint or breakpoint triggers. */ void arm_debug_excp_handler(CPUState *cs); diff --git a/target-arm/machine.c b/target-arm/machine.c index 8dfe87c..ddb7d05 100644 --- a/target-arm/machine.c +++ b/target-arm/machine.c @@ -214,6 +214,7 @@ static int cpu_post_load(void *opaque, int version_id) } } + hw_breakpoint_update_all(cpu); hw_watchpoint_update_all(cpu); return 0; -- 2.7.4