This is an automated email from Gerrit. "JC Delaunay <[email protected]>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/9513
-- gerrit commit f544b52fd7215e07842c93f4fd91bcd5332702bf Author: JC Delaunay <[email protected]> Date: Thu Mar 5 17:09:38 2026 +0100 src/target/{many} Add watchpoints support for Cortex A/R targets Files modified: src/target/breakpoints.c src/target/cortex_a.c src/target/cortex_a.h src/target/target.h This commit adds watchpoints support for Cortex A/R targets. Previous work on this subject implemented many structures and mechanisms but lacked the main part: how to associate a trigger with a specific watchpoint. Indeed, Cortex A/R ref. manual states that on a watchpoint debug event, DFAR and IFAR hold "Unpredictable value". WFAR is set to the instruction which triggered the exception but there is no direct way to identify the related data. To address this problem this commit computes the data manipulated by the faulty instruction. It relies on existing code for arm mode (arm_disassembler.c) and on the capstone library for thumb2 mode. Currently, LDR/STR variants are handled, along with LDM/STM and STRD/LDRD ones. Change-Id: I8e8faa1ca3705b64f71606c73df6a3e5daf4daad Signed-off-by: JC Delaunay <[email protected]> diff --git a/src/target/breakpoints.c b/src/target/breakpoints.c index d260009f5a..167669db19 100644 --- a/src/target/breakpoints.c +++ b/src/target/breakpoints.c @@ -313,7 +313,7 @@ static int breakpoint_remove_internal(struct target *target, target_addr_t addre while (breakpoint) { if ((breakpoint->address == address) || - (breakpoint->address == 0 && breakpoint->asid == address)) + (breakpoint->address == 0 && breakpoint->asid == address)) break; breakpoint = breakpoint->next; } @@ -524,6 +524,13 @@ static int watchpoint_add_internal(struct target *target, target_addr_t address, case ERROR_TARGET_RESOURCE_NOT_AVAILABLE: reason = "resource not available"; goto bye; + case ERROR_TARGET_FEATURE_NOT_SUPPORTED: + reason = "feature not supported"; + goto bye; + case ERROR_TARGET_WP_CAPSTONE_REQUIRED: + reason = "watchpoint support requires Capstone. Run 'configure' script " + "with '--with-capstone' option and build openocd again"; + goto bye; case ERROR_TARGET_NOT_HALTED: reason = "target not halted"; goto bye; diff --git a/src/target/cortex_a.c b/src/target/cortex_a.c index 2efbcce0c9..20fefa5f99 100644 --- a/src/target/cortex_a.c +++ b/src/target/cortex_a.c @@ -26,12 +26,16 @@ * [email protected] * * * * Copyright (C) 2016 Chengyu Zheng * - * [email protected] : watchpoint support * + * [email protected] : watchpoint support skeleton * + * * + * Copyright (C) 2026 JC Delaunay * + * [email protected] : watchpoint support * * * * Cortex-A8(tm) TRM, ARM DDI 0344H * * Cortex-A9(tm) TRM, ARM DDI 0407F * * Cortex-A4(tm) TRM, ARM DDI 0363E * * Cortex-A15(tm)TRM, ARM DDI 0438C * + * Cortex-R5(tm)TRM, ARM DDI 0460D * * * ***************************************************************************/ @@ -76,16 +80,10 @@ static int cortex_a_virt2phys(struct target *target, static int cortex_a_read_cpu_memory(struct target *target, uint32_t address, uint32_t size, uint32_t count, uint8_t *buffer); -static unsigned int ilog2(unsigned int x) -{ - unsigned int y = 0; - x /= 2; - while (x) { - ++y; - x /= 2; - } - return y; -} +#if HAVE_CAPSTONE +#include "arm_disassembler.h" +#include "capstone.h" +#endif /* restore cp15_control_reg at resume */ static int cortex_a_restore_cp15_control_reg(struct target *target) @@ -1404,8 +1402,8 @@ static int cortex_a_set_breakpoint(struct target *target, * https://developer.arm.com/documentation/den0013/d/Porting/Endianness */ if ((((cortex_a->cpuid & CPUDBG_CPUID_MASK) == CPUDBG_CPUID_CORTEX_R4) || - ((cortex_a->cpuid & CPUDBG_CPUID_MASK) == CPUDBG_CPUID_CORTEX_R5)) && - target->endianness == TARGET_BIG_ENDIAN) { + ((cortex_a->cpuid & CPUDBG_CPUID_MASK) == CPUDBG_CPUID_CORTEX_R5)) && + target->endianness == TARGET_BIG_ENDIAN) { // In place swapping is allowed buf_bswap32(code, code, 4); } @@ -1751,6 +1749,18 @@ static int cortex_a_remove_breakpoint(struct target *target, struct breakpoint * return ERROR_OK; } +#if HAVE_CAPSTONE +static inline unsigned int ilog2(unsigned int x) +{ + unsigned int y = 0; + x /= 2; + while (x) { + ++y; + x /= 2; + } + return y; +} + /** * Sets a watchpoint for an Cortex-A target in one of the watchpoint units. It is * considered a bug to call this function when there are no available watchpoint @@ -1769,15 +1779,36 @@ static int cortex_a_set_watchpoint(struct target *target, struct watchpoint *wat uint32_t address; uint8_t address_mask; uint8_t byte_address_select; + uint8_t load_store_access_control = 0x3; struct cortex_a_common *cortex_a = target_to_cortex_a(target); struct armv7a_common *armv7a = &cortex_a->armv7a_common; struct cortex_a_wrp *wrp_list = cortex_a->wrp_list; + uint8_t wp_length; if (watchpoint->is_set) { LOG_WARNING("watchpoint already set"); return retval; } + switch (watchpoint->rw) { + // load, load exclusive, or swap + case WPT_READ: + load_store_access_control = 0x1; + break; + // store, store exclusive or swap + case WPT_WRITE: + load_store_access_control = 0x2; + break; + // both + case WPT_ACCESS: + load_store_access_control = 0x3; + break; + // should not occur + default: + LOG_ERROR("Watchpoint->rw neither read, write nor access"); + return ERROR_FAIL; + } + /* check available context WRPs */ while (wrp_list[wrp_i].used && (wrp_i < cortex_a->wrp_num)) wrp_i++; @@ -1794,12 +1825,13 @@ static int cortex_a_set_watchpoint(struct target *target, struct watchpoint *wat } if (watchpoint->address & (watchpoint->length - 1)) { - LOG_WARNING("watchpoint address must be aligned at length"); + LOG_WARNING("BUG: watchpoint address must be aligned at length"); return ERROR_FAIL; } /* FIXME: ARM DDI 0406C: address_mask is optional. What to do if it's missing? */ /* handle wp length 1 and 2 through byte select */ + wp_length = watchpoint->length; switch (watchpoint->length) { case 1: byte_address_select = BIT(watchpoint->address & 0x3); @@ -1808,6 +1840,7 @@ static int cortex_a_set_watchpoint(struct target *target, struct watchpoint *wat break; case 2: + // this addresses only the 2 couple low bits byte_address_select = 0x03 << (watchpoint->address & 0x2); address = watchpoint->address & ~0x3; address_mask = 0; @@ -1823,25 +1856,10 @@ static int cortex_a_set_watchpoint(struct target *target, struct watchpoint *wat byte_address_select = 0xff; address = watchpoint->address; address_mask = ilog2(watchpoint->length); + wp_length = 0xff; break; } - uint8_t load_store_access_control; - switch (watchpoint->rw) { - case WPT_READ: - load_store_access_control = 1; - break; - case WPT_WRITE: - load_store_access_control = 2; - break; - case WPT_ACCESS: - load_store_access_control = 3; - break; - default: - LOG_ERROR("BUG: watchpoint->rw neither read, write nor access"); - return ERROR_FAIL; - }; - watchpoint_set(watchpoint, wrp_i); control = (address_mask << 24) | (byte_address_select << 5) | @@ -1850,6 +1868,7 @@ static int cortex_a_set_watchpoint(struct target *target, struct watchpoint *wat wrp_list[wrp_i].used = true; wrp_list[wrp_i].value = address; wrp_list[wrp_i].control = control; + wrp_list[wrp_i].length = wp_length; retval = mem_ap_write_atomic_u32(armv7a->debug_ap, armv7a->debug_base + CPUDBG_WVR_BASE + 4 * wrp_list[wrp_i].wrpn, @@ -1869,6 +1888,7 @@ static int cortex_a_set_watchpoint(struct target *target, struct watchpoint *wat return ERROR_OK; } +#endif /** * Unset an existing watchpoint and clear the used watchpoint unit. @@ -1925,6 +1945,7 @@ static int cortex_a_unset_watchpoint(struct target *target, struct watchpoint *w */ static int cortex_a_add_watchpoint(struct target *target, struct watchpoint *watchpoint) { +#if HAVE_CAPSTONE struct cortex_a_common *cortex_a = target_to_cortex_a(target); if (cortex_a->wrp_num_available < 1) { @@ -1938,8 +1959,12 @@ static int cortex_a_add_watchpoint(struct target *target, struct watchpoint *wat cortex_a->wrp_num_available--; return ERROR_OK; +#else + return ERROR_TARGET_WP_CAPSTONE_REQUIRED; +#endif } +#if HAVE_CAPSTONE /** * Remove a watchpoint from an Cortex-A target. The watchpoint will be unset and * the used watchpoint unit will be reopened. @@ -1959,6 +1984,1076 @@ static int cortex_a_remove_watchpoint(struct target *target, struct watchpoint * return ERROR_OK; } +static inline int get_bit_pos(uint32_t input) +{ + int val = 0; + for (; val != 8; val++) { + if (((input >> val) & 0x1) == 0x1) + return val; + } + return -1; +} + +/** + * Compares previously resolved addresses with watchpoints currently + * defined + * + * @param wrp Pointer to `cortex_a_wrp` which holds watchpoints info + * @param target_addr Array holding all addresses resolved + * @param nb_addr Number of element in aformentioned array + * @return Error status + */ +static inline int cortex_a_is_watchpoint_matching(struct cortex_a_wrp *wrp, + uint32_t *target_addr, + uint8_t nb_addr) +{ + uint8_t mask; + /** + * 'Byte address select' field from Watchpoint Control Register + * permits to watch unaligned data. + * The current implementation handles 1, 2 and 4 bytes length + * watchpoint even though gdb isn't happy about that + */ + uint32_t byte_address_select = (wrp->control >> 5) & 0xff; + + for (uint8_t i = 0; i < nb_addr; i++) { + switch (wrp->length) { + case 1: + uint8_t bit_pos = get_bit_pos(byte_address_select); + mask = bit_pos > 3 ? ~0x7 : ~0x3; + if (((wrp->value & mask) + bit_pos) == target_addr[i]) + return ERROR_OK; + break; + + case 2: + mask = byte_address_select == 3 ? ~0x3 : ~0xc; + if ((wrp->value & mask) == (target_addr[i] & mask)) + return ERROR_OK; + break; + + case 4: + // first 2 bits are reserved + if ((wrp->value & ~0x3) == (target_addr[i] & ~0x3)) + return ERROR_OK; + break; + default: + return ERROR_FAIL; + } + } + return ERROR_FAIL; +} + +static inline uint8_t cortex_a_get_watchpoint_access_type(struct cortex_a_wrp *wrp) +{ + return (wrp->control >> 3) & 0x3; +} + +static inline int cs_is_load_insn(const cs_insn *insn) +{ + if (insn->id >= ARM_INS_LDR + && insn->id <= ARM_INS_LDRT + && insn->id != ARM_INS_LDRD) + return ERROR_OK; + + return ERROR_FAIL; +} + +static inline int cs_is_store_insn(const cs_insn *insn) +{ + if (insn->id >= ARM_INS_STR + && insn->id <= ARM_INS_STRT + && insn->id != ARM_INS_STRD) + return ERROR_OK; + + return ERROR_FAIL; +} + +static inline int cs_is_load_misc_insn(const cs_insn *insn) +{ + if (insn->id == ARM_INS_LDRD) + return ERROR_OK; + + return ERROR_FAIL; +} + +static inline int cs_is_store_misc_insn(const cs_insn *insn) +{ + if (insn->id == ARM_INS_STRD) + return ERROR_OK; + + return ERROR_FAIL; +} + +static inline int cs_is_load_store_insn(const cs_insn *insn) +{ + if ((cs_is_load_insn(insn) == ERROR_OK) + || (cs_is_store_insn(insn) == ERROR_OK)) + return ERROR_OK; + + return ERROR_FAIL; +} + +static inline int cs_is_load_multiple_insn(const cs_insn *insn) +{ + if (insn->id >= ARM_INS_LDM && insn->id <= ARM_INS_LDMIB) + return ERROR_OK; + + return ERROR_FAIL; +} + +static inline int cs_is_load_store_misc_insn(const cs_insn *insn) +{ + if (cs_is_load_misc_insn(insn) == ERROR_OK + || cs_is_store_misc_insn(insn) == ERROR_OK) + return ERROR_OK; + + return ERROR_FAIL; +} + +static inline int cs_is_store_multiple_insn(const cs_insn *insn) +{ + if (insn->id >= ARM_INS_STM && insn->id <= ARM_INS_STMIB) + return ERROR_OK; + + return ERROR_FAIL; +} + +static inline int cs_is_load_store_multiple_insn(const cs_insn *insn) +{ + if (cs_is_load_multiple_insn(insn) == ERROR_OK + || cs_is_store_multiple_insn(insn) == ERROR_OK) + return ERROR_OK; + + return ERROR_FAIL; +} + +/** + * Associates capstone instruction type with openocd `arm_instruction.type` + * + * @param ocd_instruction Pointer to openocd instruction object + * @param cs_instruction Pointer to capstone instruction object + * @return Error status + */ +static int map_cs_insn_type_to_ocd_insn_type(struct arm_instruction *ocd_instruction, + cs_insn *cs_instruction) +{ + switch (cs_instruction->id) { + case ARM_INS_LDRBT: + ocd_instruction->type = ARM_LDRBT; + break; + case ARM_INS_LDREXB: + case ARM_INS_LDRB: + ocd_instruction->type = ARM_LDRB; + break; + case ARM_INS_LDREXD: + case ARM_INS_LDRD: + ocd_instruction->type = ARM_LDRD; + break; + case ARM_INS_LDREXH: + case ARM_INS_LDRHT: + case ARM_INS_LDRH: + ocd_instruction->type = ARM_LDRH; + break; + case ARM_INS_LDRSBT: + case ARM_INS_LDRSB: + ocd_instruction->type = ARM_LDRSB; + break; + case ARM_INS_LDRSHT: + case ARM_INS_LDRSH: + ocd_instruction->type = ARM_LDRSH; + break; + case ARM_INS_LDRT: + ocd_instruction->type = ARM_LDRT; + break; + case ARM_INS_LDREX: + case ARM_INS_LDR: + ocd_instruction->type = ARM_LDR; + break; + case ARM_INS_STRBT: + ocd_instruction->type = ARM_STRBT; + break; + case ARM_INS_STREXB: + case ARM_INS_STRB: + ocd_instruction->type = ARM_STRB; + break; + case ARM_INS_STREXD: + case ARM_INS_STRD: + ocd_instruction->type = ARM_STRD; + break; + case ARM_INS_STREXH: + case ARM_INS_STRHT: + case ARM_INS_STRH: + ocd_instruction->type = ARM_STRH; + break; + case ARM_INS_STRT: + ocd_instruction->type = ARM_STRT; + break; + case ARM_INS_STREX: + case ARM_INS_STR: + ocd_instruction->type = ARM_STR; + break; + case ARM_INS_STMDA: + case ARM_INS_STMDB: + case ARM_INS_STMIB: + case ARM_INS_STM: + ocd_instruction->type = ARM_STM; + break; + case ARM_INS_LDMDA: + case ARM_INS_LDMDB: + case ARM_INS_LDMIB: + case ARM_INS_LDM: + ocd_instruction->type = ARM_LDM; + break; + default: + return ERROR_FAIL; + } + return ERROR_OK; +} + +/** + * Uses capstone lib output to populate openocd arm instruction object. + * Dedicated to str/ldr and most of their variations. + * + * @param ldstmi InOut pointer to struct `arm_load_store_instr` to be + * populated + * @param insn Pointer to capstone instruction object + * @param wfar Address of Watchpoint Fault Address Register (WFAR) + * @return Error status + */ +static int thumb2_populate_load_store_insn_struct(struct arm_load_store_instr *ldstri, + const cs_insn *insn, + uint32_t wfar) +{ + int retval; + retval = cs_is_load_store_insn(insn); + + if (retval != ERROR_OK) { + LOG_ERROR("Not a LDR/STR insn at 0x%08X:\n%s\t%s", + wfar, + insn->mnemonic, + insn->op_str); + return ERROR_FAIL; + } + + const cs_arm * arm = &insn->detail->arm; + + // Expect at least: op0 = Rd, op1 = [Rn, offset] + if (arm->op_count < 2) + return ERROR_FAIL; + + cs_arm_op op0 = arm->operands[0]; // Rd + cs_arm_op op1 = arm->operands[1]; // Mem operand + + /* Rd */ + if (op0.type == ARM_OP_REG) + ldstri->rd = op0.reg; + + /* Rn */ + if (op1.type == ARM_OP_MEM) + ldstri->rn = op1.mem.base - ARM_REG_R0; // ARM_REG_R0 index is 0x42 + + /* Index mode */ + if (!arm->writeback) + ldstri->index_mode = 0; // offset addressing + else if (op1.mem.disp != 0) + ldstri->index_mode = 1; // pre-index + else + ldstri->index_mode = 2; // post-index + + /* U bit */ + ldstri->u = (op1.mem.disp >= 0) ? 1 : 0; + + /* Offset: immediate vs register */ + if (op1.mem.index == ARM_REG_INVALID) { + // Immediate offset + ldstri->offset_mode = 0; + ldstri->offset.offset = abs(op1.mem.disp); + } else { + // Register offset + ldstri->offset_mode = 1; + ldstri->offset.reg.rm = op1.mem.index - ARM_REG_R0; + ldstri->offset.reg.shift = op1.shift.type; // LSL=0, LSR=1, ... + ldstri->offset.reg.shift_imm = op1.shift.value; + } + + // corner cases with PC-relative or SP-relative + if (op1.mem.base == ARM_REG_PC) { + // PC-relative addressing (eg. LDR Rt, [PC, #imm]) + ldstri->rn = ARM_REG_PC; + ldstri->u = (op1.mem.disp >= 0) ? 1 : 0; // offset sign + ldstri->offset.offset = abs(op1.mem.disp); + ldstri->index_mode = 0; // offset + ldstri->offset_mode = 0; // immediate + } else if (op1.mem.base == ARM_REG_SP) { + // SP-relative addressing (eg. LDR Rt, [SP, #imm]) + ldstri->rn = ARM_REG_SP; + ldstri->u = (op1.mem.disp >= 0) ? 1 : 0; + ldstri->offset.offset = abs(op1.mem.disp); + ldstri->index_mode = 0; + ldstri->offset_mode = 0; + } + /* + if PC + offset (label) case Capstone sets base=PC and + disp=offset. This case can be encountered for insn + such as LDR Rt, =label + */ + if (op1.mem.base == ARM_REG_PC && op1.mem.disp != 0) { + // eg. LDR Rt, [PC, #imm] + ldstri->rn = ARM_REG_PC; + ldstri->u = (op1.mem.disp >= 0) ? 1 : 0; + ldstri->offset.offset = abs(op1.mem.disp); + ldstri->index_mode = 0; + ldstri->offset_mode = 0; + } + + switch (insn->id) { + case ARM_INS_LDRSB: + case ARM_INS_LDRSH: + case ARM_INS_LDRSBT: + case ARM_INS_LDRSHT: + // specific to insn with sign extent + ldstri->offset_mode = 0; // immediate + ldstri->u = 1; // positive offset + ldstri->offset.offset = 0; // no extra offset + break; + + default: + break; + } + + return ERROR_OK; +} + +/** + * Uses capstone lib output to populate openocd arm instruction object. + * Dedicated to stm/ldm and all their variations. + * + * @param ldstmi InOut pointer to struct `arm_load_store_instr` to be + * populated + * @param insn Pointer to capstone instruction object + * @param wfar Address of the Watchpoint Fault Address Register (WFAR) + * @return Error status + */ +static int thumb2_populate_load_store_misc_insn_struct(struct arm_load_store_instr *ldstri, + const cs_insn *insn, + uint32_t wfar) +{ + int retval; + retval = cs_is_load_store_misc_insn(insn); + + if (retval != ERROR_OK) { + LOG_ERROR("Not a LDRD/STRD insn at 0x%08X:\n%s\t%s", + wfar, + insn->mnemonic, + insn->op_str); + return ERROR_FAIL; + } + + const cs_arm * arm = &insn->detail->arm; + + // Expect at least: op0 = Rd, op1 = Rd2, op2 = [Rn, offset] + if (arm->op_count != 3) + return ERROR_FAIL; + + cs_arm_op op0 = arm->operands[0]; // Rd + //cs_arm_op op1 = arm->operands[1]; // Rd2 + cs_arm_op op2 = arm->operands[2]; // Mem operand + + /* Rd */ + if (op0.type == ARM_OP_REG) + ldstri->rd = op0.reg; + + /* Rd2 does not exist in 'arm_load_store_instr' struct */ + + /* Rn */ + if (op2.type == ARM_OP_MEM) + ldstri->rn = op2.mem.base - ARM_REG_R0; + + /* Index mode */ + if (!arm->writeback) + ldstri->index_mode = 0; // offset addressing + else if (op2.mem.disp != 0) + ldstri->index_mode = 1; // pre-index + else + ldstri->index_mode = 2; // post-index + + /* U bit: offset sign */ + ldstri->u = (op2.mem.disp >= 0) ? 1 : 0; + + /* Offset: only immediate */ + ldstri->offset_mode = 0; + ldstri->offset.offset = abs(op2.mem.disp); + + // PC-relative addressing (eg. Rd, Rd2, [PC, #imm]) + if (op2.mem.base == ARM_REG_PC) { + ldstri->rn = ARM_REG_PC; + ldstri->index_mode = 0; // enforce offset + } + // SP-relative addressing (eg. LDR Rd, Rd2, [SP, #imm]) + else if (op2.mem.base == ARM_REG_SP) { + ldstri->rn = ARM_REG_SP; + ldstri->index_mode = 0; // enforce offset + } + return ERROR_OK; +} + +/** + * Uses capstone lib output to populate openocd arm instruction object. + * Handles stm/ldm instruction only. + * + * @param ldstmi InOut pointer to struct `arm_load_store_multiple_instr` to be + * populated + * @param insn Pointer to capstone instruction object + * @param wfar Address of the Watchpoint Fault Address Register (WFAR) + * @return Error status + */ +static int thumb2_populate_load_store_multiple_insn_struct(struct arm_load_store_multiple_instr *ldstmi, + const cs_insn *insn, + uint32_t wfar) +{ + int retval; + retval = cs_is_load_store_multiple_insn(insn); + + if (retval != ERROR_OK) { + LOG_ERROR("Not a LDR/STR insn at 0x%08X:\n%s\t%s", + wfar, + insn->mnemonic, + insn->op_str); + return ERROR_FAIL; + } + + const cs_arm * arm = &insn->detail->arm; + + // Expect at least: op0 = Rd, op1 = [Rn, offset] + if (arm->op_count == 0) + return ERROR_FAIL; + + ldstmi->rn = 0; + ldstmi->register_list = 0; + ldstmi->s = 0; // Is CSPR altered + ldstmi->w = 0; // Is writeback required + + /* Rn */ + cs_arm_op rn = arm->operands[0]; + if (rn.type == ARM_OP_REG) + ldstmi->rn = rn.reg; + + int j = 1; + // concerned registers + for (; j < arm->op_count; j++) { + if (arm->operands[j].type == ARM_OP_REG) + ldstmi->register_list |= (1 << (arm->operands[j].reg - ARM_REG_R0)); + else + break; + } + if (j != arm->op_count) { + LOG_ERROR("operand is not a register"); + return ERROR_FAIL; + } + + switch (insn->id) { + // Increment After + case ARM_INS_LDM: + case ARM_INS_STM: + ldstmi->addressing_mode = 0; + break; + // Increment Before + case ARM_INS_LDMIB: + case ARM_INS_STMIB: + ldstmi->addressing_mode = 1; + break; + // Decrement After + case ARM_INS_LDMDA: + case ARM_INS_STMDA: + ldstmi->addressing_mode = 2; + break; + case ARM_INS_LDMDB: + case ARM_INS_STMDB: + ldstmi->addressing_mode = 3; + break; + default: + ldstmi->addressing_mode = 0; + break; + } + + // if (arm->address_mode == ARM_AM_POST_INDEXED) + // addressing_mode = 0; // IA : Increment After + // else if (arm->address_mode == ARM_AM_PRE_INDEXED) + // addressing_mode = 1; // IB : Increment Before + // else if (arm->address_mode == ARM_AM_NEGATIVE) + // addressing_mode = 2; // DA : Decrement After + // else if (arm->address_mode == ARM_AM_POSITIVE) + // addressing_mode = 3; // DB : Decrement Before + + ldstmi->s = arm->update_flags ? 1 : 0; + ldstmi->w = arm->writeback ? 1 : 0; + + return ERROR_OK; +} + +/** + * Evaluate the addresses manipulated by an LDM/STM instruction + * or one of their variations + * + * @param target Pointer to the Cortex-A target + * @param instruction Pointer to the `arm_instruction` struct describing + * instruction + * @param access_addr Output pointer to address(es) manipulated by instruction + * @return Error status + */ +static int cortex_a_watchpoint_fetch_ldm_stm_hit_addr(struct target *target, + struct arm_instruction *instruction, + uint32_t *access_addr) +{ + struct armv7a_common *armv7a = target_to_armv7a(target); + + /* we first fetch source addr Rn */ + struct reg *rn = NULL; + uint32_t modified_address = 0; + + rn = arm_reg_current(&armv7a->arm, instruction->info.load_store_multiple.rn); + if (!rn) { + LOG_ERROR("could not read content of R%d", + instruction->info.load_store_multiple.rn); + return ERROR_FAIL; + } + + modified_address = *(uint32_t *)(rn->value); + int step = 4; + + switch (instruction->info.load_store_multiple.addressing_mode) { + case 0: /* IA: Increment after */ + /* rn = rn; */ + break; + case 1: /* IB: Increment before */ + modified_address += 4; + break; + case 2: /* DA: Decrement after */ + step = -4; + break; + case 3: /* DB: Decrement before */ + step = -4; + modified_address -= 4; + break; + } + + // bitfield representing src/dst registers + int nb_regs = 0; + + for (int i = 0; i < 16; i++) { + if (instruction->info.load_store_multiple.register_list & (1 << i)) { + access_addr[nb_regs++] = modified_address; + modified_address += step; + } + } + return nb_regs; +} + +/** + * Evaluate the addresses manipulated by an LDRD/STRD instruction + * + * @param target Pointer to the Cortex-A target + * @param instruction Pointer to the `arm_instruction` struct describing + * instruction + * @param access_addr Output pointer to address(es) manipulated by instruction + * @return Error status + */ +static int cortex_a_watchpoint_fetch_ldrd_strd_hit_addr(struct target *target, + struct arm_instruction *instruction, + uint32_t *access_addr) +{ + struct armv7a_common *armv7a = target_to_armv7a(target); + + /* we first fetch source addr Rn */ + struct reg *rn = NULL; + uint8_t offset_sign; + uint32_t modified_address = 0; + + rn = arm_reg_current(&armv7a->arm, instruction->info.load_store.rn); + if (!rn) { + LOG_ERROR("could not read content of R%d", + instruction->info.load_store.rn); + return ERROR_FAIL; + } + + modified_address = *(uint32_t *)(rn->value); + /* sign of offset */ + offset_sign = (instruction->info.load_store.u != 0) ? 1 : -1; + + modified_address += + offset_sign * instruction->info.load_store.offset.offset; + + access_addr[0] = modified_address; + access_addr[1] = modified_address + 4; + + return ERROR_OK; +} + +/** + * Evaluate the address(es) manipulated by an LDR/STR instruction + * or one of their variations + * + * @param target Pointer to the Cortex-A target + * @param instruction Pointer to the `arm_instruction` struct describing + * instruction + * @param access_addr Output pointer to address(es) manipulated by instruction + * @return Error status + */ +static int cortex_a_watchpoint_fetch_ldr_str_hit_addr(struct target *target, + struct arm_instruction *instruction, + uint32_t *access_addr) +{ + struct armv7a_common *armv7a = target_to_armv7a(target); + + /* we first fetch source addr Rn */ + struct reg *rn = NULL; + uint8_t offset_sign; + uint32_t modified_address = 0; + + rn = arm_reg_current(&armv7a->arm, instruction->info.load_store.rn); + if (!rn) { + LOG_ERROR("could not read content of R%d", + instruction->info.load_store.rn); + return ERROR_FAIL; + } + + modified_address = *(uint32_t *)(rn->value); + /* sign of offset */ + offset_sign = (instruction->info.load_store.u != 0) ? 1 : -1; + + /* [Rn +- #<.offset>] */ + /* eg. LDR R0, [R1, #4] => addr = R1 + 4 */ + if (instruction->info.load_store.offset_mode == 0) // 0: immediate + modified_address += + offset_sign * instruction->info.load_store.offset.offset; + + /* [Rn +- [+-<Rm>, <Shift>, #<shift_imm>]] */ + else if (instruction->info.load_store.offset_mode == 1) { // 1: (scaled) register + struct reg *rm = NULL; + uint8_t shift_imm, shift; + uint32_t rm_val, shift_offset; + rm = arm_reg_current(&armv7a->arm, + instruction->info.load_store.offset.reg.rm); + if (!rm) { + LOG_ERROR("could not read content of R%d", + instruction->info.load_store.offset.reg.rm); + return ERROR_FAIL; + } + rm_val = *(uint32_t *)(rm->value); + + shift_imm = instruction->info.load_store.offset.reg.shift_imm; + shift = instruction->info.load_store.offset.reg.shift; + /* [Rn +- <Rm>] */ + if (shift_imm == 0x0 && shift == 0x0) { + modified_address += offset_sign * rm_val; + } else { + /* eg. LDR Rd, [Rn, Rm, Shift_operation #shift_imm] */ + switch (shift) { + /* eg. LDR R0, [R1, R2, LSL #2] => addr = R1 + (R2 << 2) */ + case 0x0: /* LSL */ + shift_offset = rm_val << shift_imm; + break; + /* eg. LDR R0, [R1, R2, LSR #2] => addr = R1 + (R2 >> 2) */ + case 0x1: /* LSR */ + shift_offset = rm_val >> shift_imm; + break; + /* eg. LDR R0, [R1, R2, ASR #2] => addr = R1 + (R2 >> 3) */ + case 0x2: /* ASR */ + shift_offset = (int32_t)rm_val >> shift_imm; + break; + /* eg. LDR R0, [R1, R2, ROR #1] => addr = R1 + (ROR(R2, 1)) */ + case 0x3: /* ROR */ + shift_offset = + (rm_val >> shift_imm) | (rm_val << (32 - shift_imm)); + break; + case 0x4: /* RRX */ + shift_offset = (rm_val >> 1) | (rm_val << 31); + break; + default: + /* should not occur */ + shift_offset = rm_val; + break; + } + modified_address += offset_sign * shift_offset; + return ERROR_OK; + } + } + + LOG_DEBUG("decoded insn index mode: %d", instruction->info.load_store.index_mode); + + /* 0: offset */ + if (instruction->info.load_store.index_mode == 0) { + /* standard case, base address register unchanged */ + *access_addr = modified_address; + return ERROR_OK; + } + /* 1: pre-indexed: addr computed before */ + /* + eg. LDR R0, [R1, #4]! => addr = R1 + 4 and R1 += 4 after read/write + access + */ + else if (instruction->info.load_store.index_mode == 1) { + /* same as index_mode == 0 but kept for readiness */ + *access_addr = modified_address; + return ERROR_OK; + } + /* 2: post-indexed: addr computed after */ + /* eg. LDR R0, [R1], #4 => addr = R1 and R1 += 4 after read/write access */ + else if (instruction->info.load_store.index_mode == 2) { + /* do nothing as memory access is afterwards */ + *access_addr = *(uint32_t *)(rn->value); + return ERROR_OK; + } + + return ERROR_FAIL; +} + +/** + * Uses Capstone to disassemble opcodes in thumb2 mode. + * Because target address computation is similar in + * arm and thumb2, we want to be able to use the same fn + * for the parsing, whichever current mode. + * To do that we have to populate the `arm_instruction` + * structure with Capstone parsing results. This function is here + * to achieve this task. + * + * @param opcode Opc value + * @param address Addr of the Watchpoint Fault Address Register (WFAR) + * @param instruction InOut pointer to the `arm_instruction` struct to be + * populated + * @return Error status + */ +static int thumb2_evaluate_opcode(uint32_t opcode, + uint32_t address, + struct arm_instruction *instruction) +{ + csh handle; + int retval; + cs_insn *cpstn_insn = NULL; + cs_mode mode; + uint32_t wfar = address; + + memset(instruction, 0, sizeof(struct arm_instruction)); + instruction->opcode = opcode; + + if (!cs_support(CS_ARCH_ARM)) { + LOG_ERROR("ARM architecture not supported by capstone"); + return ERROR_FAIL; + } + + mode = CS_MODE_LITTLE_ENDIAN | CS_MODE_THUMB; + + retval = cs_open(CS_ARCH_ARM, mode, &handle); + + if (retval != CS_ERR_OK) { + LOG_ERROR("cs_open() failed: %s", cs_strerror(retval)); + return ERROR_FAIL; + } + + retval = cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON); + + if (retval != CS_ERR_OK) { + LOG_ERROR("cs_option() failed: %s", cs_strerror(retval)); + cs_close(&handle); + return ERROR_FAIL; + } + + cpstn_insn = cs_malloc(handle); + + if (!cpstn_insn) { + LOG_ERROR("cs_malloc() failed\n"); + cs_close(&handle); + return ERROR_FAIL; + } + + size_t size = sizeof(opcode); + int nb_insn = 1; + + int count = cs_disasm(handle, (uint8_t *)&opcode, size, wfar, nb_insn, &cpstn_insn); + + if (count != nb_insn) { + LOG_ERROR("cs_disasm() failed: %s", cs_strerror(cs_errno(handle))); + cs_free(cpstn_insn, 1); + cs_close(&handle); + return ERROR_FAIL; + } + + /* we fill remaining fields of 'instruction' object */ + instruction->instruction_size = cpstn_insn->size; + if (cs_is_load_store_insn(cpstn_insn) == ERROR_OK) { + struct arm_load_store_instr ldstri; + retval = thumb2_populate_load_store_insn_struct(&ldstri, cpstn_insn, wfar); + if (retval != ERROR_OK) { + LOG_ERROR("failed populating insn internal structure from LDR/STR " + "Capstone insn"); + cs_free(cpstn_insn, 1); + cs_close(&handle); + return ERROR_FAIL; + } + memcpy(&instruction->info.load_store, + &ldstri, + sizeof(struct arm_load_store_instr)); + } else if (cs_is_load_store_multiple_insn(cpstn_insn) == ERROR_OK) { + struct arm_load_store_multiple_instr ldstmi; + retval = + thumb2_populate_load_store_multiple_insn_struct(&ldstmi, cpstn_insn, wfar); + if (retval != ERROR_OK) { + LOG_ERROR("failed populating insn internal structure from LDM/STM " + "Capstone insn"); + cs_free(cpstn_insn, 1); + cs_close(&handle); + return ERROR_FAIL; + } + memcpy(&instruction->info.load_store_multiple, + &ldstmi, + sizeof(struct arm_load_store_multiple_instr)); + } else if (cs_is_load_store_misc_insn(cpstn_insn) == ERROR_OK) { + struct arm_load_store_instr ldstri; + retval = thumb2_populate_load_store_misc_insn_struct(&ldstri, cpstn_insn, wfar); + if (retval != ERROR_OK) { + LOG_ERROR("failed populating insn internal structure from LDRD/STRD " + "Capstone insn"); + cs_free(cpstn_insn, 1); + cs_close(&handle); + return ERROR_FAIL; + } + memcpy(&instruction->info.load_store, + &ldstri, + sizeof(struct arm_load_store_instr)); + } + + char mnemonic[32] = {0}; + char operand[32] = {0}; + + memcpy(mnemonic, &cpstn_insn->mnemonic, 31); + memcpy(operand, &cpstn_insn->op_str, 31); + + snprintf(instruction->text, + 128, + "0x%8.8" PRIx32 "\t0x%8.8" PRIx32 "\t%s %s", + wfar, + opcode, + mnemonic, + operand); + + /* fill arm_instruction.type */ + retval = map_cs_insn_type_to_ocd_insn_type(instruction, cpstn_insn); + + if (retval != ERROR_OK) { + LOG_ERROR("failed mapping cs insn type to openocd insn type"); + cs_free(cpstn_insn, 1); + cs_close(&handle); + return ERROR_FAIL; + } + + cs_free(cpstn_insn, 1); + cs_close(&handle); + + return ERROR_OK; +} + +/** + * Retrieves instruction associated with Watchpoint Fault Address Register + * + * @param target Pointer to the Cortex-A target + * @param wfar Address of the WFAR + * @param instruction InOut pointer to the `arm_instruction` struct to be + * populated + * @return Error status + */ +static int cortex_a_watchpoint_hit_fetch_instr(struct target *target, + uint32_t wfar, + struct arm_instruction *instruction) +{ + struct armv7a_common *armv7a = target_to_armv7a(target); + struct arm *arm = &armv7a->arm; + int retval; + uint32_t opcode; + + if (arm->core_state == ARM_STATE_ARM) { + retval = target_read_u32(target, wfar, &opcode); + if (retval != ERROR_OK) { + LOG_ERROR("could not fetch (ARM) opcodes at 0x%08X", wfar); + return retval; + } + retval = arm_evaluate_opcode(opcode, wfar, instruction); + if (retval != ERROR_OK) { + LOG_ERROR("could not evaluate (ARM) opcodes at 0x%08X", wfar); + return retval; + } + return ERROR_OK; + } else if (arm->core_state == ARM_STATE_THUMB) { + retval = target_read_u32(target, wfar, &opcode); + if (retval != ERROR_OK) { + LOG_ERROR("could not fetch (Thumb2) opcodes at 0x%08X", wfar); + return retval; + } + retval = thumb2_evaluate_opcode(opcode, wfar, instruction); + if (retval != ERROR_OK) { + LOG_ERROR("could not evaluate (Thumb2) opcodes at 0x%08X", wfar); + return retval; + } + return ERROR_OK; + } + + LOG_ERROR("instr type is not handled (yet):\n%s", instruction->text); + + return ERROR_FAIL; +} + +/** + * Main routine of watchpoints handling. Fetch instructions + * and computes addresses to be able to associate trigger point + * with existing watchpoint. + * + * @param target Pointer to the Cortex-A target + * @param watchpoint struct holding all information about watchpoints sets + * @return Error status + */ +static int cortex_a_hit_watchpoint(struct target *target, + struct watchpoint **hit_watchpoint) +{ + if (target->debug_reason != DBG_REASON_WATCHPOINT) + return ERROR_FAIL; + + int wrp_i; + int retval; + + struct cortex_a_common *cortex_a = target_to_cortex_a(target); + struct armv7a_common *armv7a = &cortex_a->armv7a_common; + struct cortex_a_wrp *wrp_list = cortex_a->wrp_list; + uint32_t wfar; + + struct arm_instruction instruction; + int hit_access_type; + uint8_t nb_addr = 1; + + retval = mem_ap_read_atomic_u32(armv7a->debug_ap, + armv7a->debug_base + CPUDBG_WFAR, + &wfar); + if (retval != ERROR_OK) { + LOG_ERROR("error fetching WFAR reg."); + return retval; + } + + if (armv7a->arm.core_state == ARM_STATE_ARM) { + wfar -= 8; + } else if (armv7a->arm.core_state == ARM_STATE_THUMB) { + wfar -= 4; + } else { + LOG_ERROR("Core in an unknown state (%d)", armv7a->arm.core_state); + return ERROR_FAIL; + } + + retval = cortex_a_watchpoint_hit_fetch_instr(target, wfar, &instruction); + if (retval != ERROR_OK) + return ERROR_FAIL; + + /* + Prevents subsequent computation if watchpoint + and hitpoint access types don't match + */ + + if (instruction.type >= ARM_LDR + && instruction.type < ARM_LDM) { + hit_access_type = CORTEX_A_INSTR_LOAD_TYPE; + } else if (instruction.type >= ARM_STR + && instruction.type < ARM_STM) { + hit_access_type = CORTEX_A_INSTR_STORE_TYPE; + } else if (instruction.type == ARM_LDRD) { + hit_access_type = CORTEX_A_INSTR_LDRD_TYPE; + } else if (instruction.type == ARM_STRD) { + hit_access_type = CORTEX_A_INSTR_STRD_TYPE; + } else if (instruction.type == ARM_LDM) { + hit_access_type = CORTEX_A_INSTR_LOAD_MULTIPLE_TYPE; + } else if (instruction.type == ARM_STM) { + hit_access_type = CORTEX_A_INSTR_STORE_MULTIPLE_TYPE; + } else { + LOG_ERROR("instr type is not handled (yet?):\n%s", instruction.text); + return ERROR_FAIL; + } + + uint32_t *hit_address = 0; + uint32_t ldr_str_hit_address = 0; + uint32_t ldrd_stmd_hit_addresses[2] = {0}; + uint32_t ldm_stm_hit_addresses[16] = {0}; + + if (hit_access_type == CORTEX_A_INSTR_LOAD_TYPE || + hit_access_type == CORTEX_A_INSTR_STORE_TYPE) { + retval = cortex_a_watchpoint_fetch_ldr_str_hit_addr(target, + &instruction, + &ldr_str_hit_address); + if (retval != ERROR_OK) { + LOG_ERROR("LDR/STR failed retrieving watched addr at 0x%08X:\n%s", + wfar, + instruction.text); + return ERROR_FAIL; + } + nb_addr = 1; + hit_address = &ldr_str_hit_address; + } else if (hit_access_type == CORTEX_A_INSTR_LDRD_TYPE || + hit_access_type == CORTEX_A_INSTR_STRD_TYPE) { + retval = cortex_a_watchpoint_fetch_ldrd_strd_hit_addr(target, + &instruction, + (uint32_t *)&ldrd_stmd_hit_addresses); + if (retval != ERROR_OK) { + LOG_ERROR("LDRD/STRD failed retrieving watched addr at 0x%08X:\n%s", + wfar, + instruction.text); + return ERROR_FAIL; + } + nb_addr = 2; + hit_address = (uint32_t *)&ldrd_stmd_hit_addresses; + } else { + retval = cortex_a_watchpoint_fetch_ldm_stm_hit_addr(target, + &instruction, + (uint32_t *)&ldm_stm_hit_addresses); + if (retval == ERROR_FAIL) { + LOG_ERROR("LDM/STM failed retrieving watched addresses at 0x%08X:\n%s", + wfar, + instruction.text); + return ERROR_FAIL; + } + nb_addr = retval; + hit_address = (uint32_t *)&ldm_stm_hit_addresses; + } + + /* check available context WRPs */ + for (wrp_i = 0; wrp_i < cortex_a->wrp_num; wrp_i++) { + if (!wrp_list[wrp_i].used) + continue; + for (struct watchpoint *wp = target->watchpoints; wp; wp = wp->next) { + if (wp->is_set + && wp->number == wrp_list[wrp_i].wrpn) { + uint8_t wp_access_type; + wp_access_type = + cortex_a_get_watchpoint_access_type(&wrp_list[wrp_i]); + /* we don't even bother to fetch the addr */ + if (wp_access_type == CORTEX_A_WP_WRITE + && (hit_access_type == CORTEX_A_INSTR_LOAD_TYPE + || hit_access_type == CORTEX_A_INSTR_LOAD_MULTIPLE_TYPE + || hit_access_type == CORTEX_A_INSTR_LDRD_TYPE)) + continue; + + retval = cortex_a_is_watchpoint_matching(&wrp_list[wrp_i], + hit_address, + nb_addr); + if (retval == ERROR_OK) { + *hit_watchpoint = wp; + return ERROR_OK; + } + } + } + } + + LOG_ERROR("could not find any matching watchpoint, wp detection " + "failure at 0x%08X, (computed watched addr: 0x%08X):\n%s", + wfar, + hit_address[0], + instruction.text); + return ERROR_FAIL; +} +#endif /* * Cortex-A Reset functions @@ -3047,7 +4142,7 @@ static int cortex_a_examine_first(struct target *target) cortex_a->cpuid = cpuid; retval = mem_ap_read_atomic_u32(armv7a->debug_ap, - armv7a->debug_base + CPUDBG_PRSR, &dbg_osreg); + armv7a->debug_base + CPUDBG_PRSR, &dbg_osreg); if (retval != ERROR_OK) return retval; LOG_TARGET_DEBUG(target, "DBGPRSR 0x%" PRIx32, dbg_osreg); @@ -3323,7 +4418,7 @@ static int cortex_a_virt2phys(struct target *target, if (retval != ERROR_OK) return retval; return armv7a_mmu_translate_va_pa(target, (uint32_t)virt, - phys, 1); + phys, 1); } COMMAND_HANDLER(cortex_a_handle_cache_info_command) @@ -3489,8 +4584,16 @@ struct target_type cortexa_target = { .add_context_breakpoint = cortex_a_add_context_breakpoint, .add_hybrid_breakpoint = cortex_a_add_hybrid_breakpoint, .remove_breakpoint = cortex_a_remove_breakpoint, + /* + we have to keep this entry, even if watchpoints are not + supported, in order to notice caller that feature is + not supported + */ .add_watchpoint = cortex_a_add_watchpoint, +#if HAVE_CAPSTONE .remove_watchpoint = cortex_a_remove_watchpoint, + .hit_watchpoint = cortex_a_hit_watchpoint, +#endif .commands = cortex_a_command_handlers, .target_create = cortex_a_target_create, @@ -3566,8 +4669,16 @@ struct target_type cortexr4_target = { .add_context_breakpoint = cortex_a_add_context_breakpoint, .add_hybrid_breakpoint = cortex_a_add_hybrid_breakpoint, .remove_breakpoint = cortex_a_remove_breakpoint, + /* + we have to keep this entry, even if watchpoints are not + supported, in order to notice caller that feature is + not supported + */ .add_watchpoint = cortex_a_add_watchpoint, +#if HAVE_CAPSTONE .remove_watchpoint = cortex_a_remove_watchpoint, + .hit_watchpoint = cortex_a_hit_watchpoint, +#endif .commands = cortex_r4_command_handlers, .target_create = cortex_r4_target_create, diff --git a/src/target/cortex_a.h b/src/target/cortex_a.h index 8e438fd958..8487aa584f 100644 --- a/src/target/cortex_a.h +++ b/src/target/cortex_a.h @@ -62,13 +62,44 @@ struct cortex_a_brp { uint8_t brpn; }; +/** + Represents a Watchpoint Register Pair. Holds values + of 2 different registers: + - Watchpoint Value Register (DBGWVR) + - Watchpoint Control Register (DBGWCR) +*/ struct cortex_a_wrp { bool used; + /** + * Holds watchpoint addr: + * [31:2] Watchpoint address + * [1:0] Reserved + */ uint32_t value; uint32_t control; + uint8_t length; uint8_t wrpn; }; +#if HAVE_CAPSTONE +enum cortex_a_watchpoint_instr_type { + CORTEX_A_INSTR_UNEXPECTED = -1, + // ERROR_OK == 0 + CORTEX_A_INSTR_LOAD_TYPE = 1, + CORTEX_A_INSTR_STORE_TYPE, + CORTEX_A_INSTR_LDRD_TYPE, + CORTEX_A_INSTR_STRD_TYPE, + CORTEX_A_INSTR_LOAD_MULTIPLE_TYPE, + CORTEX_A_INSTR_STORE_MULTIPLE_TYPE, +}; + +enum cortex_a_watchpoint_type { + CORTEX_A_WP_READ = 1 << 0, + CORTEX_A_WP_WRITE = 1 << 1, + CORTEX_A_WP_ACCESS = CORTEX_A_WP_READ | CORTEX_A_WP_WRITE, +}; +#endif + struct cortex_a_common { unsigned int common_magic; diff --git a/src/target/target.h b/src/target/target.h index 2b66dd741c..6214693e81 100644 --- a/src/target/target.h +++ b/src/target/target.h @@ -806,6 +806,8 @@ int target_profiling_default(struct target *target, uint32_t *samples, uint32_t #define ERROR_TARGET_PACKING_NOT_SUPPORTED (-315) #define ERROR_TARGET_HALTED_DO_RESUME (-316) /* used to workaround incorrect debug halt */ #define ERROR_TARGET_INTERSECT_BREAKPOINT (-317) +#define ERROR_TARGET_FEATURE_NOT_SUPPORTED (-318) +#define ERROR_TARGET_WP_CAPSTONE_REQUIRED (-319) extern bool get_target_reset_nag(void); --
