/* * Copyright (c) 2016-2017 Chris Johns . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define TARGET_DEBUG 0 #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "rtems-debugger-target.h" #include "rtems-debugger-threads.h" #if TARGET_DEBUG #include #endif /* * ARM Variant controls. */ #if defined(__ARM_ARCH_7A__) || \ defined(__ARM_ARCH_7R__) #define ARM_CP15 1 #endif #if (defined(__ARM_ARCH_7M__) || \ defined(__ARM_ARCH_7EM__)) #define ARM_THUMB_ONLY 1 #else #define ARM_THUMB_ONLY 0 #endif #if defined(ARM_MULTILIB_ARCH_V4) #define ARM_PSR_HAS_INT_MASK 1 #define ARM_PSR_HAS_THUMB 1 #else #define ARM_PSR_HAS_INT_MASK 0 #define ARM_PSR_HAS_THUMB 0 #endif #if ARM_CP15 #include #endif /** * If thumb build of code switch the asm to thumb as required. * * If the variant only supports thumb insturctions disable the support. */ #define ARM_SWITCH_REG uint32_t arm_switch_reg #define ARM_SWITCH_REG_ASM [arm_switch_reg] "=&r" (arm_switch_reg) #if !ARM_THUMB_ONLY && defined(__thumb__) #define ASM_ARM_MODE ".align 2\nbx pc\n.arm\n" #define ASM_THUMB_MODE "add %[arm_switch_reg], pc, #1\nbx %[arm_switch_reg]\n.thumb\n" #else #define ASM_ARM_MODE #define ASM_THUMB_MODE #endif /* * Hack to work around ARMv7-M not having a the T and I bits in the PSR. * * This needs to be fixed when real support for this ARM variant is added. */ #if !defined(ARM_PSR_I) #define ARM_PSR_I 0 #endif #if !defined(ARM_PSR_T) #define ARM_PSR_T 0 #endif /* * The ARM has 2 interrupt bits. */ #define CPSR_IRQ_DISABLE 0x80 /* IIQ disabled when 1 */ #define CPSR_FIQ_DISABLE 0x40 /* FIQ disabled when 1 */ #define CPSR_INTS_MASK (CPSR_IRQ_DISABLE | CPSR_FIQ_DISABLE) /* * Software breakpoint block size. */ #define RTEMS_DEBUGGER_SWBREAK_NUM 64 /* * Number of registers. */ #define RTEMS_DEBUGGER_NUMREGS 26 /* * Number of bytes per register. */ #define RTEMS_DEBUGGER_REGBYTES 4 /* * Number of bytes of registers. */ #define RTEMS_DEBUGGER_NUMREGBYTES \ (RTEMS_DEBUGGER_NUMREGS * RTEMS_DEBUGGER_REGBYTES) /* * Debugger registers layout. */ #define REG_R0 0 #define REG_R1 1 #define REG_R2 2 #define REG_R3 3 #define REG_R4 4 #define REG_R5 5 #define REG_R6 6 #define REG_R7 7 #define REG_R8 8 #define REG_R9 9 #define REG_R10 10 #define REG_R11 11 #define REG_R12 12 #define REG_SP 13 #define REG_LR 14 #define REG_PC 15 #define REG_CPSR 25 /** * The various status registers. */ #if defined(ARM_MULTILIB_ARCH_V4) #define FRAME_SR frame->register_cpsr #elif defined(ARM_MULTILIB_ARCH_V7M) #define FRAME_SR frame->register_xpsr #else #error ARM architecture is not supported. #endif /** * The breakpoint. */ #ifdef __thumb__ static const uint8_t breakpoint[2] = { 0x55, 0xbe }; #else static const uint8_t breakpoint[4] = { 0x75, 0xe0, 0x20, 0xe1 }; #endif /** * Target lock. */ RTEMS_INTERRUPT_LOCK_DEFINE(static, target_lock, "target_lock") /** * Is a session active? */ static bool debug_session_active; /* * ARM debug hardware. */ static int debug_version; static int debug_revision;; static int hw_breakpoints; static int hw_watchpoints; /** * Hardware break and watch points. */ typedef struct { bool enabled; bool loaded; void* address; size_t length; CPU_Exception_frame* frame; uint32_t control; uint32_t value; } arm_debug_hwbreak; #define ARM_HW_BREAKPOINT_MAX (16) #define ARM_HW_WATCHPOINT_MAX (16) /* * Types of break points. Only the 2 we use listed. */ #define ARM_HW_BP_UNLINKED_INSTR_MATCH (0x00) #define ARM_HW_BP_UNLINKED_INSTR_MISMATCH (0x04) /* * Privilege levels. */ #define ARM_HW_BP_PRIV_PL0_SUP_SYS (0x00) #define ARM_HW_BP_PRIV_PL1_ONLY (0x01) #define ARM_HW_BP_PRIV_PL0_ONLY (0x02) #define ARM_HW_BP_PRIV_ALL_MODES (0x03) static arm_debug_hwbreak hw_breaks[ARM_HW_BREAKPOINT_MAX]; //static arm_debug_hwbreak hw_watches[ARM_HW_WATCHPOINT_MAX]; #if TARGET_DEBUG void rtems_debugger_printk_lock(rtems_interrupt_lock_context* lock_context); void rtems_debugger_printk_unlock(rtems_interrupt_lock_context* lock_context); static void target_printk(const char* format, ...) RTEMS_PRINTFLIKE(1, 2); static void target_printk(const char* format, ...) { rtems_interrupt_lock_context lock_context; va_list ap; va_start(ap, format); rtems_debugger_printk_lock(&lock_context); vprintk(format, ap); rtems_debugger_printk_unlock(&lock_context); va_end(ap); } static const char* mode_label(int mode) { switch (mode) { case 0x10: return "USR"; case 0x11: return "FIQ"; case 0x12: return "IRQ"; case 0x13: return "SVC"; case 0x16: return "MON"; case 0x17: return "ABT"; case 0x1a: return "HYP"; case 0x1b: return "UND"; case 0x1f: return "SYS"; } return "---"; } #else #define target_printk(_fmt, ...) #define mode_labels(_m) NULL #endif /* * CP register access. */ #define ARM_CP_INSTR(_opc, _cp, _op1, _val, _CRn, _CRm, _op2) \ #_opc " p" #_cp ", " #_op1 ", %[" #_val "], c" #_CRn ", c" #_CRm ", " #_op2 "\n" #define ARM_CP_WRITE(_cp, _op1, _val, _CRn, _CRm, _op2) \ do { \ ARM_SWITCH_REG; \ asm volatile( \ ASM_ARM_MODE \ ARM_CP_INSTR(mcr, _cp, _op1, val, _CRn, _CRm, _op2) \ ASM_THUMB_MODE \ : ARM_SWITCH_REG_ASM \ : [val] "r" (_val)); \ } while (0) #define ARM_CP_READ(_cp, _op1, _val, _CRn, _CRm, _op2) \ do { \ ARM_SWITCH_REG; \ asm volatile( \ ASM_ARM_MODE \ ARM_CP_INSTR(mrc, _cp, _op1, val, _CRn, _CRm, _op2) \ ASM_THUMB_MODE \ : ARM_SWITCH_REG_ASM, \ [val] "=&r" (_val)); \ } while (0) /* * Read and write a CP14 register. * * The software debug event registers are not easy to program because there are * up to 32 registers and the instructions have to assembler for each of the 32 * registers, you cannot program it. This means there is a switch table to do * this. */ #define ARM_CP14_WRITE(_val, _CRn, _CRm, _op2) \ ARM_CP_WRITE(14, 0, _val, _CRn, _CRm, _op2) #define ARM_CP14_READ(_val, _CRn, _CRm, _op2) \ ARM_CP_READ(14, 0, _val, _CRn, _CRm, _op2) /* * Read and write a CP15 register. * * The Context ID register is a process level context and used to scope * hardware break points. */ #define ARM_CP15_WRITE(_val, _op1, _CRn, _CRm, _op2) \ ARM_CP_WRITE(15, _op1, _val, _CRn, _CRm, _op2) #define ARM_CP15_READ(_val, _op1, _CRn, _CRm, _op2) \ ARM_CP_READ(15, _op1, _val, _CRn, _CRm, _op2) static int arm_debug_probe(rtems_debugger_target* target) { #define ID_VALUE(_i, _h, _l) ((_i >> _l) & ((1 << ((_h - _l) + 1)) -1)) uint32_t val; const char* vl = "[Invalid version]"; const char* const labels[] = { "ARMv6 [v6]", "ARMv6 [v6.1]", "ARMv7 [v7, all CP14 registers]", "ARMv7 [v7, baseline CP14 registers]", "ARMv7 [v7.1]" }; ARM_CP14_READ(val, 0, 0, 0); debug_version = ID_VALUE(val, 19, 16); if (debug_version < 1 || debug_version > 5) { rtems_debugger_printf("rtems-db: arm debug: (v%d.%d) not supported\n", debug_version, debug_revision); errno = EIO; return -1; } vl = labels[debug_version - 1]; debug_revision = ID_VALUE(val, 3, 0); hw_breakpoints = ID_VALUE(val, 27, 24); hw_watchpoints = ID_VALUE(val, 31, 28); rtems_debugger_printf("rtems-db: arm debug: (v%d.%d) %s breakpoints:%d watchpoints:%d\n", debug_version, debug_revision, vl, hw_breakpoints, hw_watchpoints); ARM_CP14_READ(val, 0, 1, 0); if ((val & (1 << 15)) == 0) { switch (debug_version) { case 1: case 2: ARM_CP14_WRITE(val | (1 << 15), 0, 1, 0); break; case 3: case 4: case 5: default: ARM_CP14_WRITE(val | (1 << 15), 0, 2, 2); break; } ARM_CP14_READ(val, 0, 1, 0); if ((val & (1 << 15)) == 0) { rtems_debugger_printf("rtems-db: arm debug: cannot enter monitor mode\n"); errno = EIO; return -1; } } return 0; } static inline void arm_debug_break_setup(arm_debug_hwbreak* bp, uint32_t address, uint32_t type, uint32_t byte_address_select, uint32_t privilege) { bp->control = (((type & 0xf) << 20) | ((byte_address_select & 0xf) << 5) | ((privilege & 0x3) << 1) | 1); bp->value = (intptr_t) address; } static void arm_debug_break_write_control(int bp, uint32_t control) { switch (bp) { case 0: ARM_CP14_WRITE(control, 0, 0, 5); break; case 1: ARM_CP14_WRITE(control, 0, 1, 5); break; case 2: ARM_CP14_WRITE(control, 0, 2, 5); break; case 3: ARM_CP14_WRITE(control, 0, 3, 5); break; case 4: ARM_CP14_WRITE(control, 0, 4, 5); break; case 5: ARM_CP14_WRITE(control, 0, 5, 5); break; case 6: ARM_CP14_WRITE(control, 0, 6, 5); break; case 7: ARM_CP14_WRITE(control, 0, 7, 5); break; case 8: ARM_CP14_WRITE(control, 0, 8, 5); break; case 9: ARM_CP14_WRITE(control, 0, 9, 5); break; case 10: ARM_CP14_WRITE(control, 0, 10, 5); break; case 11: ARM_CP14_WRITE(control, 0, 11, 5); break; case 12: ARM_CP14_WRITE(control, 0, 12, 5); break; case 13: ARM_CP14_WRITE(control, 0, 13, 5); break; case 14: ARM_CP14_WRITE(control, 0, 14, 5); break; case 15: ARM_CP14_WRITE(control, 0, 15, 5); break; } } static void arm_debug_break_write_value(int bp, uint32_t value) { switch (bp) { case 0: ARM_CP14_WRITE(value, 0, 0, 4); break; case 1: ARM_CP14_WRITE(value, 0, 1, 4); break; case 2: ARM_CP14_WRITE(value, 0, 2, 4); break; case 3: ARM_CP14_WRITE(value, 0, 3, 4); break; case 4: ARM_CP14_WRITE(value, 0, 4, 4); break; case 5: ARM_CP14_WRITE(value, 0, 5, 4); break; case 6: ARM_CP14_WRITE(value, 0, 6, 4); break; case 7: ARM_CP14_WRITE(value, 0, 7, 4); break; case 8: ARM_CP14_WRITE(value, 0, 8, 4); break; case 9: ARM_CP14_WRITE(value, 0, 9, 4); break; case 10: ARM_CP14_WRITE(value, 0, 10, 4); break; case 11: ARM_CP14_WRITE(value, 0, 11, 4); break; case 12: ARM_CP14_WRITE(value, 0, 12, 4); break; case 13: ARM_CP14_WRITE(value, 0, 13, 4); break; case 14: ARM_CP14_WRITE(value, 0, 14, 4); break; case 15: ARM_CP14_WRITE(value, 0, 15, 4); break; } } static void arm_debug_break_clear(void) { rtems_interrupt_lock_context lock_context; arm_debug_hwbreak* bp = &hw_breaks[0]; int i; rtems_interrupt_lock_acquire(&target_lock, &lock_context); for (i = 0; i < hw_breakpoints; ++i, ++bp) { bp->enabled = false; bp->loaded = false; } rtems_interrupt_lock_release(&target_lock, &lock_context); } static inline void arm_debug_set_context_id(const uint32_t id) { ARM_CP15_WRITE(id, 0, 13, 0, 1); } /* * You can only load the hardware breaks points when in the SVC mode or the * single step inverted break point will trigger. */ static void arm_debug_break_load(void) { rtems_interrupt_lock_context lock_context; arm_debug_hwbreak* bp = &hw_breaks[0]; int i; rtems_interrupt_lock_acquire(&target_lock, &lock_context); if (bp->enabled && !bp->loaded) { arm_debug_set_context_id(0xdead1111); arm_debug_break_write_value(0, bp->value); arm_debug_break_write_control(0, bp->control); } ++bp; for (i = 1; i < hw_breakpoints; ++i, ++bp) { if (bp->enabled && !bp->loaded) { bp->loaded = true; arm_debug_break_write_value(i, bp->value); arm_debug_break_write_control(i, bp->control); } } rtems_interrupt_lock_release(&target_lock, &lock_context); } static void arm_debug_break_unload(void) { rtems_interrupt_lock_context lock_context; arm_debug_hwbreak* bp = &hw_breaks[0]; int i; rtems_interrupt_lock_acquire(&target_lock, &lock_context); arm_debug_set_context_id(0); for (i = 0; i < hw_breakpoints; ++i, ++bp) { bp->loaded = false; arm_debug_break_write_control(i, 0); } rtems_interrupt_lock_release(&target_lock, &lock_context); } #if NOT_USED_BUT_KEEPING static size_t arm_debug_break_length(void* pc) { arm_debug_hwbreak* bp = &hw_breaks[0]; int i; for (i = 0; i < hw_breakpoints; ++i, ++bp) { if (bp->enabled && bp->address == pc) { return bp->length; } } return sizeof(DB_UINT); } #endif int rtems_debugger_target_configure(rtems_debugger_target* target) { target->capabilities = (RTEMS_DEBUGGER_TARGET_CAP_SWBREAK); target->reg_num = RTEMS_DEBUGGER_NUMREGS; target->reg_size = sizeof(uint32_t); target->breakpoint = &breakpoint[0]; target->breakpoint_size = sizeof(breakpoint); return arm_debug_probe(target); } static void target_exception(CPU_Exception_frame* frame) { #if TARGET_DEBUG uint32_t ifsr = arm_cp15_get_instruction_fault_status(); #endif target_printk("[} frame = %08lx sig=%d vector=%x ifsr=%08lx pra=%08x\n", (uint32_t) frame, rtems_debugger_target_exception_to_signal(frame), frame->vector, ifsr, (intptr_t) frame->register_pc); if ((FRAME_SR & (1 << 5)) == 0) frame->register_pc = (void*) ((intptr_t) frame->register_pc - 8); else frame->register_pc = (void*) ((intptr_t) frame->register_pc - 4); target_printk("[} R0 = %08" PRIx32 " R1 = %08" PRIx32 \ " R2 = %08" PRIx32 " R3 = %08" PRIx32 "\n", frame->register_r0, frame->register_r1, frame->register_r2, frame->register_r3); target_printk("[} R4 = %08" PRIx32 " R5 = %08" PRIx32 \ " R6 = %08" PRIx32 " R7 = %08" PRIx32 "\n", frame->register_r4, frame->register_r5, frame->register_r6, frame->register_r7); target_printk("[} R8 = %08" PRIx32 " R9 = %08" PRIx32 \ " R10 = %08" PRIx32 " R11 = %08" PRIx32 "\n", frame->register_r8, frame->register_r9, frame->register_r10, frame->register_r11); target_printk("[} R12 = %08" PRIx32 " SP = %08" PRIx32 \ " LR = %08" PRIxPTR " PC = %08" PRIxPTR "\n", \ frame->register_r12, frame->register_sp, (intptr_t) frame->register_lr, (intptr_t) frame->register_pc); target_printk("[} CPSR = %08" PRIx32 " %c%c%c%c%c%c%c%c%c%c%c" \ " GE:%" PRIx32 " IT:%02" PRIx32 " M:%" PRIx32 " %s\n", FRAME_SR, (FRAME_SR & (1 << 31)) != 0 ? 'N' : '-', (FRAME_SR & (1 << 30)) != 0 ? 'Z' : '-', (FRAME_SR & (1 << 29)) != 0 ? 'C' : '-', (FRAME_SR & (1 << 28)) != 0 ? 'V' : '-', (FRAME_SR & (1 << 27)) != 0 ? 'Q' : '-', (FRAME_SR & (1 << 24)) != 0 ? 'J' : '-', (FRAME_SR & (1 << 9)) != 0 ? 'E' : '-', (FRAME_SR & (1 << 8)) != 0 ? 'A' : '-', (FRAME_SR & (1 << 7)) != 0 ? 'I' : '-', (FRAME_SR & (1 << 6)) != 0 ? 'F' : '-', (FRAME_SR & (1 << 5)) != 0 ? 'T' : '-', ((FRAME_SR >> (25 - 5)) & (0x3 << 5)) | ((FRAME_SR >> 10) & 0x1f), (FRAME_SR >> 16) & 0xf, FRAME_SR & 0x1f, mode_label(FRAME_SR & 0x1f)); arm_debug_break_clear(); if (!debug_session_active) _ARM_Exception_default(frame); switch (rtems_debugger_target_exception(frame)) { case rtems_debugger_target_exc_consumed: default: break; case rtems_debugger_target_exc_step: FRAME_SR |= CPSR_INTS_MASK; break; case rtems_debugger_target_exc_cascade: target_printk("rtems-db: unhandled exception: cascading\n"); _ARM_Exception_default(frame); break; } target_printk("[} resuming frame = %08lx PC = %08" PRIxPTR " CPSR = %08" PRIx32 "\n", (uint32_t) frame, (intptr_t) frame->register_pc, FRAME_SR); } /** * Exception stack frame size. * * The size is the exception stack frame plus the CPSR from the exception. We * save the CPSR and restore it when we exit the exception. */ #define EXCEPTION_FRAME_SIZE (sizeof(CPU_Exception_frame) + sizeof(uint32_t)) /** * Exception stack frame FPU offsets and sizes. */ #define EXCEPTION_FRAME_FPU_SIZE ARM_VFP_CONTEXT_SIZE #define EXCEPTION_FRAME_FPU_OFFSET ARM_EXCEPTION_FRAME_VFP_CONTEXT_OFFSET /** * Exception entry. * * We have switched from svc (or even user) to an exception mode. Save the * current CPSR and create an exception frame on the exception's stack and then * copy it to the thread's stack. Switch back to the thread's context and mode * to handle the exception to avoid any stack checks thinking the stack is * blown. This lets the thread be suspended. * * The entry is in two parts, the exception mode entry and the trhead mode * entry. This lets us disable any hardware breakpoint support. We need to do * this because it is enabled in PL0 mode. * * Note, the code currently assumes cp15 has been set up to match the * instruction set being used. */ #define EXCEPTION_ENTRY_EXC() \ __asm__ volatile( \ ASM_ARM_MODE \ "sub sp, %[frame_size]\n" /* alloc the frame and CPSR */ \ "stm sp, {r0-r12}\n" /* store r0-r12 */ \ "sub sp, #4\n" \ "str lr, [sp]\n" /* save the link reg */ \ ASM_THUMB_MODE \ : ARM_SWITCH_REG_ASM \ : [frame_size] "i" (EXCEPTION_FRAME_SIZE) \ : "memory") /* * FPU entry. Conditionally D16 or D32 support. */ #ifdef ARM_MULTILIB_VFP #ifdef ARM_MULTILIB_VFP_D32 #define FPU_ENTRY_VFP_D32 \ "vstmia r5!, {d16-d31}\n" #else /* ARM_MULTILIB_VFP_D32 */ #define FPU_ENTRY_VFP_D32 \ "mov r3, #0\n" \ "mov r4, #0\n" \ "adds r6, r5, #128\n" \ "3:\n" \ "stmia r5!, {r3-r4}\n" \ "cmp r5, r6\n" \ "bne 3b\n" #endif /* ARM_MULTILIB_VFP_D32 */ #define EXCEPTION_ENTRY_FPU(frame_fpu_size) \ "sub sp, %[frame_fpu_size]\n" /* size includes alignment size */ \ "add r5, sp, #4\n" /* up to align down */ \ "bic r5, r5, #7\n" /* align the FPU frame */ \ "str r5, [r2]\n" /* store the FPU frame pointer */ \ "vmrs r3, FPEXC\n" \ "vmrs r4, FPSCR\n" \ "stmia r5!, {r3-r4}\n" \ "vstmia r5!, {d0-d15}\n" \ FPU_ENTRY_VFP_D32 #else /* ARM_MULTILIB_VFP */ #define EXCEPTION_ENTRY_FPU(frame_fpu_size) #endif /* ARM_MULTILIB_VFP */ #define EXCEPTION_ENTRY_THREAD(_frame) \ __asm__ volatile( \ ASM_ARM_MODE \ "ldr lr, [sp]\n" /* recover the link reg */ \ "add sp, #4\n" \ "add r0, sp, %[r0_r12_size]\n" /* get the sp in the frame */ \ "mrs r1, spsr\n" /* get the saved sr */ \ "mov r6, r1\n" /* stash it for later */ \ "bic r1, r1, %[psr_t]\n" /* clear thumb mode, not sure? */ \ "orr r1, r1, %[psr_i]\n" /* mask irqs */ \ "mrs r2, cpsr\n" /* get the current sr */ \ "str r2, [sp, %[frame_cpsr]]\n" /* save for exc return */ \ "msr cpsr, r1\n" /* switch to user mode */ \ "mov r3, sp\n" /* get the stack pointer */ \ "mov r4, lr\n" /* get the link reg */ \ "msr cpsr, r2\n" /* back to exc mode */ \ "mov r5, lr\n" /* get the PRA */ \ "stm r0, {r3-r6}\n" /* save into the frame */ \ "sub r4, r3, %[frame_size]\n" /* destination address */ \ "mov r6, r4\n" /* save the frame */ \ "sub r4, #1\n" /* one before the start */ \ "add r3, #1\n" /* one past the end */ \ "sub r5, sp, #1\n" /* source address */ \ "1:\n" \ "ldrb r0, [r5, #1]!\n" /* get a byte */ \ "strb r0, [r4, #1]!\n" /* put the byte */ \ "cmp r3, r4\n" /* the end? */ \ "bne 1b\n" \ "add sp, %[frame_size]\n" /* free the frame and CPSR */ \ "mrs r1, spsr\n" /* get the thread's saved sr */ \ "orr r2, r1, %[psr_i]\n" /* mask irqs */ \ "msr cpsr, r2\n" /* switch back to the thread's context */ \ "sub sp, %[frame_size]\n" /* alloc in the thread stack */ \ "mov %[o_frame], sp\n" /* save the frame */ \ "add r2, sp, %[o_frame_fpu]\n" /* get the FPU offset */ \ "mov r3, #0\n" \ "str r3, [r2]\n" /* clear the FPU frame pointer */ \ EXCEPTION_ENTRY_FPU(frame_fpu_size) \ "bic r1, r1, %[psr_i]\n" /* clear irq mask, debug checks */ \ "msr cpsr, r1\n" /* restore the state with irq mask clear */ \ ASM_THUMB_MODE \ : ARM_SWITCH_REG_ASM, \ [o_frame] "=r" (_frame) \ : [psr_t] "i" (ARM_PSR_T), \ [psr_i] "i" (ARM_PSR_I), \ [r0_r12_size] "i" (13 * sizeof(uint32_t)), \ [frame_cpsr] "i" (EXCEPTION_FRAME_SIZE - sizeof(uint32_t)), \ [frame_size] "i" (EXCEPTION_FRAME_SIZE), \ [o_frame_fpu] "i" (EXCEPTION_FRAME_FPU_OFFSET), \ [frame_fpu_size] "i" (EXCEPTION_FRAME_FPU_SIZE + 4) \ : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "memory") /* * FPU exit. Conditionally D16 or D32 support. */ #ifdef ARM_MULTILIB_VFP #ifdef ARM_MULTILIB_VFP_D32 #define FPU_EXIT_VFP_D32 \ "vldmia r0, {d16-d31}\n" #else /* ARM_MULTILIB_VFP_D32 */ #define FPU_EXIT_VFP_D32 #endif /* ARM_MULTILIB_VFP_D32 */ #define EXCEPTION_EXIT_FPU(frame_fpu_size) \ "ldmia r0!, {r1-r2}\n" \ "vldmia r0!, {d0-d15}\n" \ FPU_EXIT_VFP_D32 \ "vmsr FPEXC, r1\n" \ "vmsr FPSCR, r2\n" \ "add sp, %[frame_fpu_size]\n" /* size includes alignment size */ #else /* ARM_MULTILIB_VFP */ #define EXCEPTION_EXIT_FPU(frame_fpu_size) #endif /* ARM_MULTILIB_VFP */ /** * Exception exit. * * The thread is to be resumed so we are still in the thread's mode. Copy the * exception frame from the thread's stack back to the exception's stack and * restore the thread's context before returning from the exception to the * thread. * * Note, the code currently assumes cp15 has been set up to match the * instruction set being used. */ #define EXCEPTION_EXIT_THREAD(_frame) \ __asm__ volatile( \ ASM_ARM_MODE \ "mov r0, %[i_frame]\n" /* get the frame */ \ "ldr r0, [r0, %[frame_fpu]]\n" /* recover FPU frame pointer */ \ EXCEPTION_EXIT_FPU(frame_fpu_size) \ "ldr r2, [sp, %[frame_cpsr]]\n" /* recover exc CPSR from thread */ \ "mov r0, sp\n" /* get the thread frame pointer */ \ "msr cpsr, r2\n" /* switch back to the exc's context */ \ "add r3, sp, #1\n" /* get the end */ \ "sub sp, %[frame_size]\n" /* alloc the frame */ \ "sub r4, sp, #1\n" /* destination address */ \ "sub r5, r0, #1\n" /* source address */ \ "1:\n" \ "ldrb r1, [r5, #1]!\n" /* get a byte */ \ "strb r1, [r4, #1]!\n" /* put the byte */ \ "cmp r3, r4\n" /* the end? */ \ "bne 1b\n" \ "add r1, r0, %[r0_r12_size]\n" /* get the sp in the frame */ \ "ldm r1, {r3-r6}\n" /* recover from the frame */ \ "orr r1, r6, %[psr_i]\n" /* get the thread's psr and mask irqs */ \ "msr cpsr, r1\n" /* switch to user mode */ \ "mov sp, r3\n" /* set the stack pointer */ \ "mov lr, r4\n" /* set the link reg */ \ "msr cpsr, r2\n" /* switch back to the exc's context */ \ "msr spsr, r6\n" /* set the thread's CPSR */ \ "sub sp, #4\n" \ "mov lr, r5\n" /* get the PC */ \ "str lr, [sp]\n" /* save the link reg */ \ ASM_THUMB_MODE \ : ARM_SWITCH_REG_ASM \ : [psr_i] "i" (ARM_PSR_I), \ [r0_r12_size] "i" (13 * sizeof(uint32_t)), \ [frame_cpsr] "i" (EXCEPTION_FRAME_SIZE - sizeof(uint32_t)), \ [frame_size] "i" (EXCEPTION_FRAME_SIZE), \ [frame_fpu] "i" (EXCEPTION_FRAME_FPU_OFFSET), \ [frame_fpu_size] "i" (EXCEPTION_FRAME_FPU_SIZE + 4), \ [i_frame] "r" (_frame) \ : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "memory") #define EXCEPTION_EXIT_EXC() \ __asm__ volatile( \ ASM_ARM_MODE \ "ldr lr, [sp]\n" /* recover the link reg */ \ "add sp, #4\n" \ "ldm sp, {r0-r12}\n" /* restore the trhead's context */ \ "add sp, %[frame_size]\n" /* free the frame */ \ "subs pc, lr, #0\n" /* return from the exc */ \ : \ : [frame_size] "i" (EXCEPTION_FRAME_SIZE) \ : "memory") static void __attribute__((naked)) target_exception_undefined_instruction(void) { CPU_Exception_frame* frame; ARM_SWITCH_REG; EXCEPTION_ENTRY_EXC(); arm_debug_break_unload(); EXCEPTION_ENTRY_THREAD(frame); frame->vector = 1; target_exception(frame); EXCEPTION_EXIT_THREAD(frame); arm_debug_break_load(); EXCEPTION_EXIT_EXC(); } static void __attribute__((naked)) target_exception_supervisor_call(void) { CPU_Exception_frame* frame; ARM_SWITCH_REG; /* * The PC offset needs to be review so we move past a svc instruction. This * can then used as a user breakpoint. The issue is this exception is used by * the BKPT instruction in the prefetch abort handler to signal a TRAP. */ EXCEPTION_ENTRY_EXC(); arm_debug_break_unload(); EXCEPTION_ENTRY_THREAD(frame); frame->vector = 2; target_exception(frame); EXCEPTION_EXIT_THREAD(frame); arm_debug_break_load(); EXCEPTION_EXIT_EXC(); } static void __attribute__((naked)) target_exception_prefetch_abort(void) { CPU_Exception_frame* frame; ARM_SWITCH_REG; EXCEPTION_ENTRY_EXC(); arm_debug_break_unload(); EXCEPTION_ENTRY_THREAD(frame); #if ARM_CP15 if ((arm_cp15_get_instruction_fault_status() & 0x1f) == 0x02) frame->vector = 2; else frame->vector = 3; #else frame->vector = 3; #endif target_exception(frame); EXCEPTION_EXIT_THREAD(frame); arm_debug_break_load(); EXCEPTION_EXIT_EXC(); } static void __attribute__((naked)) target_exception_data_abort(void) { CPU_Exception_frame* frame; ARM_SWITCH_REG; EXCEPTION_ENTRY_EXC(); arm_debug_break_unload(); EXCEPTION_ENTRY_THREAD(frame); frame->vector = 4; target_exception(frame); EXCEPTION_EXIT_THREAD(frame); arm_debug_break_load(); EXCEPTION_EXIT_EXC(); } #if ARM_CP15 /** * The init value for the text section. */ static uint32_t text_section_flags; static void rtems_debugger_target_set_vectors(void) { void* text_begin; void* text_end; text_begin = &bsp_section_text_begin[0]; text_end = &bsp_section_text_end[0]; text_section_flags = arm_cp15_set_translation_table_entries(text_begin, text_end, ARMV7_MMU_DATA_READ_WRITE_CACHED); arm_cp15_set_exception_handler(ARM_EXCEPTION_UNDEF, target_exception_undefined_instruction); arm_cp15_set_exception_handler(ARM_EXCEPTION_SWI, target_exception_supervisor_call); arm_cp15_set_exception_handler(ARM_EXCEPTION_PREF_ABORT, target_exception_prefetch_abort); arm_cp15_set_exception_handler(ARM_EXCEPTION_DATA_ABORT, target_exception_data_abort); } #else static void rtems_debugger_target_set_vectors(void) { /* * Dummy, please add support for your ARM variant. */ void* ui = target_exception_undefined_instruction; void* sc = target_exception_supervisor_call; void* pa = target_exception_prefetch_abort; void* da = target_exception_data_abort; (void) ui; (void) sc; (void) pa; (void) da; } #endif int rtems_debugger_target_enable(void) { rtems_interrupt_lock_context lock_context; debug_session_active = true; arm_debug_break_unload(); arm_debug_break_clear(); rtems_interrupt_lock_acquire(&target_lock, &lock_context); rtems_debugger_target_set_vectors(); rtems_interrupt_lock_release(&target_lock, &lock_context); return 0; } int rtems_debugger_target_disable(void) { rtems_interrupt_lock_context lock_context; #if DOES_NOT_WORK void* text_begin; void* text_end; #endif arm_debug_break_unload(); arm_debug_break_clear(); rtems_interrupt_lock_acquire(&target_lock, &lock_context); debug_session_active = false; #if DOES_NOT_WORK text_begin = &bsp_section_text_begin[0]; text_end = &bsp_section_text_end[0]; arm_cp15_set_translation_table_entries(text_begin, text_end, text_section_flags); #endif rtems_interrupt_lock_release(&target_lock, &lock_context); return 0; } int rtems_debugger_target_read_regs(rtems_debugger_thread* thread) { if (!rtems_debugger_thread_flag(thread, RTEMS_DEBUGGER_THREAD_FLAG_REG_VALID)) { static const uint32_t good_address = (uint32_t) &good_address; uint32_t* regs = &thread->registers[0]; int i; for (i = 0; i < RTEMS_DEBUGGER_NUMREGS; ++i) regs[i] = (uint32_t) &good_address; if (thread->frame) { CPU_Exception_frame* frame = thread->frame; /* * Assume interrupts are not masked and if masked set them to the saved * value. */ FRAME_SR &= ~CPSR_INTS_MASK; if (rtems_debugger_thread_flag(thread, RTEMS_DEBUGGER_THREAD_FLAG_INTS_DISABLED)) { FRAME_SR |= (thread->flags >> RTEMS_DEBUGGER_THREAD_FLAG_TARGET_BASE) & CPSR_INTS_MASK; thread->flags = ~RTEMS_DEBUGGER_THREAD_FLAG_INTS_DISABLED; } regs[REG_R0] = frame->register_r0; regs[REG_R1] = frame->register_r1; regs[REG_R2] = frame->register_r2; regs[REG_R3] = frame->register_r3; regs[REG_R4] = frame->register_r4; regs[REG_R5] = frame->register_r5; regs[REG_R6] = frame->register_r6; regs[REG_R7] = frame->register_r7; regs[REG_R8] = frame->register_r8; regs[REG_R9] = frame->register_r9; regs[REG_R10] = frame->register_r10; regs[REG_R11] = frame->register_r11; regs[REG_R12] = frame->register_r12; regs[REG_SP] = frame->register_sp; regs[REG_LR] = (uint32_t) frame->register_lr; regs[REG_PC] = (uint32_t) frame->register_pc; regs[REG_CPSR] = FRAME_SR; /* * Get the signal from the frame. */ thread->signal = rtems_debugger_target_exception_to_signal(frame); } else { #if defined(ARM_MULTILIB_ARCH_V4) regs[REG_R4] = thread->tcb->Registers.register_r4; regs[REG_R5] = thread->tcb->Registers.register_r5; regs[REG_R6] = thread->tcb->Registers.register_r6; regs[REG_R7] = thread->tcb->Registers.register_r7; regs[REG_R8] = thread->tcb->Registers.register_r8; regs[REG_R9] = thread->tcb->Registers.register_r9; regs[REG_R10] = thread->tcb->Registers.register_r10; regs[REG_R11] = thread->tcb->Registers.register_fp; regs[REG_LR] = (intptr_t) thread->tcb->Registers.register_lr; regs[REG_PC] = (intptr_t) thread->tcb->Registers.register_lr; regs[REG_SP] = (intptr_t) thread->tcb->Registers.register_sp; #elif defined(ARM_MULTILIB_ARCH_V7M) regs[REG_R4] = thread->tcb->Registers.register_r4; regs[REG_R5] = thread->tcb->Registers.register_r5; regs[REG_R6] = thread->tcb->Registers.register_r6; regs[REG_R7] = thread->tcb->Registers.register_r7; regs[REG_R8] = thread->tcb->Registers.register_r8; regs[REG_R9] = thread->tcb->Registers.register_r9; regs[REG_R10] = thread->tcb->Registers.register_r10; regs[REG_R11] = thread->tcb->Registers.register_r11; regs[REG_LR] = (intptr_t) thread->tcb->Registers.register_lr; regs[REG_PC] = (intptr_t) thread->tcb->Registers.register_lr; regs[REG_SP] = (intptr_t) thread->tcb->Registers.register_sp; #endif /* * Blocked threads have no signal. */ thread->signal = 0; } thread->flags |= RTEMS_DEBUGGER_THREAD_FLAG_REG_VALID; thread->flags &= ~RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY; } return 0; } int rtems_debugger_target_write_regs(rtems_debugger_thread* thread) { if (rtems_debugger_thread_flag(thread, RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY)) { uint32_t* regs = &thread->registers[0]; /* * Only write to debugger controlled exception threads. Do not touch the * registers for threads blocked in the context switcher. */ if (rtems_debugger_thread_flag(thread, RTEMS_DEBUGGER_THREAD_FLAG_EXCEPTION)) { CPU_Exception_frame* frame = thread->frame; frame->register_r0 = regs[REG_R0]; frame->register_r1 = regs[REG_R1]; frame->register_r2 = regs[REG_R2]; frame->register_r3 = regs[REG_R3]; frame->register_r4 = regs[REG_R4]; frame->register_r5 = regs[REG_R5]; frame->register_r6 = regs[REG_R6]; frame->register_r7 = regs[REG_R7]; frame->register_r8 = regs[REG_R8]; frame->register_r9 = regs[REG_R9]; frame->register_r10 = regs[REG_R10]; frame->register_r11 = regs[REG_R11]; frame->register_r12 = regs[REG_R12]; frame->register_sp = regs[REG_SP]; frame->register_lr = (void*) regs[REG_LR]; frame->register_pc = (void*) regs[REG_PC]; FRAME_SR = regs[REG_CPSR]; } thread->flags &= ~RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY; } return 0; } DB_UINT rtems_debugger_target_reg_pc(rtems_debugger_thread* thread) { int r; r = rtems_debugger_target_read_regs(thread); if (r >= 0) { uint32_t* regs = &thread->registers[0]; return regs[REG_PC]; } return 0; } DB_UINT rtems_debugger_target_frame_pc(CPU_Exception_frame* frame) { return (DB_UINT) frame->register_pc; } DB_UINT rtems_debugger_target_reg_sp(rtems_debugger_thread* thread) { int r; r = rtems_debugger_target_read_regs(thread); if (r >= 0) { uint32_t* regs = &thread->registers[0]; return regs[REG_SP]; } return 0; } DB_UINT rtems_debugger_target_tcb_sp(rtems_debugger_thread* thread) { return (DB_UINT) thread->tcb->Registers.register_sp; } int rtems_debugger_target_thread_stepping(rtems_debugger_thread* thread) { if (rtems_debugger_thread_flag(thread, RTEMS_DEBUGGER_THREAD_FLAG_STEP_INSTR)) { /* * Single stepping and range stepping uses hardware debug breakpoint * 0. This is reserved for single stepping. */ CPU_Exception_frame* frame = thread->frame; arm_debug_hwbreak* bp = &hw_breaks[0]; target_printk("[} stepping: %s\n", bp->enabled ? "yes" : "no"); if (!bp->enabled) { const uint32_t addr = (intptr_t) frame->register_pc; const bool thumb = (FRAME_SR & (1 << 5)) != 0 ? true : false; uint32_t bas; bp->enabled = true; bp->loaded = false; bp->address = frame->register_pc; bp->frame = frame; bp->length = sizeof(uint32_t); if (thumb) { uint16_t instr = *((uint16_t*) frame->register_pc); switch (instr & 0xf800) { case 0xe800: case 0xf000: case 0xf800: break; default: bp->length = sizeof(uint16_t); break; } } /* * See table C3-2 Effect of byte address selection on Breakpoint * generation and "Instruction address comparision programming * examples. */ if (thumb) { if ((addr & (1 << 1)) == 0) { bas = 0x3; /* b0011 */ } else { bas = 0xc; /* b1100 */ } } else { bas = 0xf; /* b1111 */ } arm_debug_break_setup(bp, addr & ~0x3, ARM_HW_BP_UNLINKED_INSTR_MISMATCH, bas, ARM_HW_BP_PRIV_PL0_SUP_SYS); /* * Save the interrupt state before stepping if set. */ #if ARM_PSR_HAS_INT_MASK if ((FRAME_SR & CPSR_INTS_MASK) != 0) { uint32_t int_state; int_state = (frame->register_cpsr & CPSR_INTS_MASK) << RTEMS_DEBUGGER_THREAD_FLAG_TARGET_BASE; thread->flags |= RTEMS_DEBUGGER_THREAD_FLAG_INTS_DISABLED | int_state; } /* * Mask the interrupt when stepping. */ FRAME_SR |= CPSR_INTS_MASK; #endif } } return 0; } int rtems_debugger_target_exception_to_signal(CPU_Exception_frame* frame) { int sig = RTEMS_DEBUGGER_SIGNAL_HUP; #if defined(ARM_MULTILIB_ARCH_V4) switch (frame->vector) { case ARM_EXCEPTION_RESET: case ARM_EXCEPTION_SWI: sig = RTEMS_DEBUGGER_SIGNAL_TRAP; break; case ARM_EXCEPTION_UNDEF: sig = RTEMS_DEBUGGER_SIGNAL_ILL; break; case ARM_EXCEPTION_FIQ: sig = RTEMS_DEBUGGER_SIGNAL_FPE; break; case ARM_EXCEPTION_PREF_ABORT: case ARM_EXCEPTION_DATA_ABORT: sig = RTEMS_DEBUGGER_SIGNAL_SEGV; break; case ARM_EXCEPTION_RESERVED: case ARM_EXCEPTION_IRQ: sig = RTEMS_DEBUGGER_SIGNAL_BUS; break; default: break; } #endif return sig; } int rtems_debugger_target_hwbreak_insert(void) { /* * Do nothing, load on exit of the exception handler. */ return 0; } int rtems_debugger_target_hwbreak_remove(void) { arm_debug_break_unload(); return 0; } int rtems_debugger_target_hwbreak_control(rtems_debugger_target_watchpoint wp, bool insert, DB_UINT addr, DB_UINT kind) { /* * To do. */ return 0; } int rtems_debugger_target_cache_sync(rtems_debugger_target_swbreak* swbreak) { /* * Flush the data cache and invalidate the instruction cache. */ rtems_cache_flush_multiple_data_lines(swbreak->address, sizeof(breakpoint)); rtems_cache_instruction_sync_after_code_change(swbreak->address, sizeof(breakpoint)); return 0; }