/* * ARM implementation of KVM and HVF hooks, 64 bit specific code * * Copyright Mian-M. Hamayun 2013, Virtual Open Systems * Copyright Alex Bennée 2014, Linaro * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. * */ #include "qemu/osdep.h" #include "cpu.h" #include "internals.h" #include "gdbstub/enums.h" /* Maximum and current break/watch point counts */ int max_hw_bps, max_hw_wps; GArray *hw_breakpoints, *hw_watchpoints; /** * insert_hw_breakpoint() * @addr: address of breakpoint * * See ARM ARM D2.9.1 for details but here we are only going to create * simple un-linked breakpoints (i.e. we don't chain breakpoints * together to match address and context or vmid). The hardware is * capable of fancier matching but that will require exposing that * fanciness to GDB's interface * * DBGBCR_EL1, Debug Breakpoint Control Registers * * 31 24 23 20 19 16 15 14 13 12 9 8 5 4 3 2 1 0 * +------+------+-------+-----+----+------+-----+------+-----+---+ * | RES0 | BT | LBN | SSC | HMC| RES0 | BAS | RES0 | PMC | E | * +------+------+-------+-----+----+------+-----+------+-----+---+ * * BT: Breakpoint type (0 = unlinked address match) * LBN: Linked BP number (0 = unused) * SSC/HMC/PMC: Security, Higher and Priv access control (Table D-12) * BAS: Byte Address Select (RES1 for AArch64) * E: Enable bit * * DBGBVR_EL1, Debug Breakpoint Value Registers * * 63 53 52 49 48 2 1 0 * +------+-----------+----------+-----+ * | RESS | VA[52:49] | VA[48:2] | 0 0 | * +------+-----------+----------+-----+ * * Depending on the addressing mode bits the top bits of the register * are a sign extension of the highest applicable VA bit. Some * versions of GDB don't do it correctly so we ensure they are correct * here so future PC comparisons will work properly. */ int insert_hw_breakpoint(target_ulong addr) { HWBreakpoint brk = { .bcr = 0x1, /* BCR E=1, enable */ .bvr = sextract64(addr, 0, 53) }; if (cur_hw_bps >= max_hw_bps) { return -ENOBUFS; } brk.bcr = deposit32(brk.bcr, 1, 2, 0x3); /* PMC = 11 */ brk.bcr = deposit32(brk.bcr, 5, 4, 0xf); /* BAS = RES1 */ g_array_append_val(hw_breakpoints, brk); return 0; } /** * delete_hw_breakpoint() * @pc: address of breakpoint * * Delete a breakpoint and shuffle any above down */ int delete_hw_breakpoint(target_ulong pc) { int i; for (i = 0; i < hw_breakpoints->len; i++) { HWBreakpoint *brk = get_hw_bp(i); if (brk->bvr == pc) { g_array_remove_index(hw_breakpoints, i); return 0; } } return -ENOENT; } /** * insert_hw_watchpoint() * @addr: address of watch point * @len: size of area * @type: type of watch point * * See ARM ARM D2.10. As with the breakpoints we can do some advanced * stuff if we want to. The watch points can be linked with the break * points above to make them context aware. However for simplicity * currently we only deal with simple read/write watch points. * * D7.3.11 DBGWCR_EL1, Debug Watchpoint Control Registers * * 31 29 28 24 23 21 20 19 16 15 14 13 12 5 4 3 2 1 0 * +------+-------+------+----+-----+-----+-----+-----+-----+-----+---+ * | RES0 | MASK | RES0 | WT | LBN | SSC | HMC | BAS | LSC | PAC | E | * +------+-------+------+----+-----+-----+-----+-----+-----+-----+---+ * * MASK: num bits addr mask (0=none,01/10=res,11=3 bits (8 bytes)) * WT: 0 - unlinked, 1 - linked (not currently used) * LBN: Linked BP number (not currently used) * SSC/HMC/PAC: Security, Higher and Priv access control (Table D2-11) * BAS: Byte Address Select * LSC: Load/Store control (01: load, 10: store, 11: both) * E: Enable * * The bottom 2 bits of the value register are masked. Therefore to * break on any sizes smaller than an unaligned word you need to set * MASK=0, BAS=bit per byte in question. For larger regions (^2) you * need to ensure you mask the address as required and set BAS=0xff */ int insert_hw_watchpoint(target_ulong addr, target_ulong len, int type) { HWWatchpoint wp = { .wcr = R_DBGWCR_E_MASK, /* E=1, enable */ .wvr = addr & (~0x7ULL), .details = { .vaddr = addr, .len = len } }; if (cur_hw_wps >= max_hw_wps) { return -ENOBUFS; } /* * HMC=0 SSC=0 PAC=3 will hit EL0 or EL1, any security state, * valid whether EL3 is implemented or not */ wp.wcr = FIELD_DP64(wp.wcr, DBGWCR, PAC, 3); switch (type) { case GDB_WATCHPOINT_READ: wp.wcr = FIELD_DP64(wp.wcr, DBGWCR, LSC, 1); wp.details.flags = BP_MEM_READ; break; case GDB_WATCHPOINT_WRITE: wp.wcr = FIELD_DP64(wp.wcr, DBGWCR, LSC, 2); wp.details.flags = BP_MEM_WRITE; break; case GDB_WATCHPOINT_ACCESS: wp.wcr = FIELD_DP64(wp.wcr, DBGWCR, LSC, 3); wp.details.flags = BP_MEM_ACCESS; break; default: g_assert_not_reached(); } if (len <= 8) { /* we align the address and set the bits in BAS */ int off = addr & 0x7; int bas = (1 << len) - 1; wp.wcr = deposit32(wp.wcr, 5 + off, 8 - off, bas); } else { /* For ranges above 8 bytes we need to be a power of 2 */ if (is_power_of_2(len)) { int bits = ctz64(len); wp.wvr &= ~((1 << bits) - 1); wp.wcr = FIELD_DP64(wp.wcr, DBGWCR, MASK, bits); wp.wcr = FIELD_DP64(wp.wcr, DBGWCR, BAS, 0xff); } else { return -ENOBUFS; } } g_array_append_val(hw_watchpoints, wp); return 0; } bool check_watchpoint_in_range(int i, target_ulong addr) { HWWatchpoint *wp = get_hw_wp(i); uint64_t addr_top, addr_bottom = wp->wvr; int bas = extract32(wp->wcr, 5, 8); int mask = extract32(wp->wcr, 24, 4); if (mask) { addr_top = addr_bottom + (1 << mask); } else { /* * BAS must be contiguous but can offset against the base * address in DBGWVR */ addr_bottom = addr_bottom + ctz32(bas); addr_top = addr_bottom + clo32(bas); } if (addr >= addr_bottom && addr <= addr_top) { return true; } return false; } /** * delete_hw_watchpoint() * @addr: address of breakpoint * * Delete a breakpoint and shuffle any above down */ int delete_hw_watchpoint(target_ulong addr, target_ulong len, int type) { int i; for (i = 0; i < cur_hw_wps; i++) { if (check_watchpoint_in_range(i, addr)) { g_array_remove_index(hw_watchpoints, i); return 0; } } return -ENOENT; } bool find_hw_breakpoint(CPUState *cpu, target_ulong pc) { int i; for (i = 0; i < cur_hw_bps; i++) { HWBreakpoint *bp = get_hw_bp(i); if (bp->bvr == pc) { return true; } } return false; } CPUWatchpoint *find_hw_watchpoint(CPUState *cpu, target_ulong addr) { int i; for (i = 0; i < cur_hw_wps; i++) { if (check_watchpoint_in_range(i, addr)) { return &get_hw_wp(i)->details; } } return NULL; }