/* * Copyright (c) 2015-2016 Wind River Systems, Inc. * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * x86 part of the GDB server */ #include #include #include #include #include #define TRACE_FLAG 0x0100 /* EFLAGS:TF */ #define INT_FLAG 0x0200 /* EFLAGS:IF */ #define INSTRUCTION_HLT 0xf4 #define INSTRUCTION_STI 0xfb #define INSTRUCTION_CLI 0xfa #ifdef GDB_ARCH_HAS_RUNCONTROL #ifdef GDB_ARCH_HAS_HW_BP static int gdb_hw_bp_find(struct gdb_debug_regs *regs, enum gdb_bp_type *bp_type, long *address); #endif #endif /** * @brief Initialize GDB server architecture part * * This routine initializes the architecture part of the GDB server. * * Does nothing currently. * * @return N/A */ void gdb_arch_init(void) { /* currently empty */ } /** * @brief Fill a GDB register set from a given ESF register set * * This routine fills the provided GDB register set with values from given * NANO_ESF register set. * * @param regs Destination GDB register set to fill * @param esf Source exception stack frame * * @return N/A */ void gdb_arch_regs_from_esf(struct gdb_reg_set *regs, NANO_ESF *esf) { regs->regs.eax = esf->eax; regs->regs.ecx = esf->ecx; regs->regs.edx = esf->edx; regs->regs.ebx = esf->ebx; regs->regs.esp = esf->esp; regs->regs.ebp = esf->ebp; regs->regs.esi = esf->esi; regs->regs.edi = esf->edi; regs->regs.eip = esf->eip; regs->regs.eflags = esf->eflags; regs->regs.cs = esf->cs; } /** * @brief Fill a GDB register set from a given ISF register set * * This routine fills the provided GDB register set with values from given * NANO_ISF register set. * * @param regs Destination GDB register set to fill * @param isf Source interrupt stack frame * * @return N/A */ void gdb_arch_regs_from_isf(struct gdb_reg_set *regs, NANO_ISF *isf) { memcpy(®s->regs, isf, sizeof(regs->regs)); } /** * @brief Fill an ESF register set from a given GDB register set * * This routine fills the provided NANO_ESF register set with values * from given GDB register set. * * @param regs Source GDB register set * @param esf Destination exception stack frame to fill * * @return N/A */ void gdb_arch_regs_to_esf(struct gdb_reg_set *regs, NANO_ESF *esf) { esf->eax = regs->regs.eax; esf->ecx = regs->regs.ecx; esf->edx = regs->regs.edx; esf->ebx = regs->regs.ebx; esf->esp = regs->regs.esp; esf->ebp = regs->regs.ebp; esf->esi = regs->regs.esi; esf->edi = regs->regs.edi; esf->eip = regs->regs.eip; esf->eflags = regs->regs.eflags; esf->cs = regs->regs.cs; } /** * @brief Fill an ISF register set from a given GDB register set * * This routine fills the provided NANO_ISF register set with values * from given GDB register set. * * @param regs Source GDB register set * @param isf Destination interrupt stack frame to fill * * @return N/A */ void gdb_arch_regs_to_isf(struct gdb_reg_set *regs, NANO_ISF *isf) { memcpy(isf, ®s->regs, sizeof(NANO_ISF)); } /** * @brief Fill given buffer from given register set * * This routine fills the provided buffer with values from given register set. * * The provided buffer must be large enough to store all register values. * It is up to the caller to do this check. * * @param regs Source GDB register set * @param esf Destination buffer to fill * * @return N/A */ void gdb_arch_regs_get(struct gdb_reg_set *regs, char *buffer) { *((u32_t *) buffer) = regs->regs.eax; buffer += 4; *((u32_t *) buffer) = regs->regs.ecx; buffer += 4; *((u32_t *) buffer) = regs->regs.edx; buffer += 4; *((u32_t *) buffer) = regs->regs.ebx; buffer += 4; *((u32_t *) buffer) = regs->regs.esp; buffer += 4; *((u32_t *) buffer) = regs->regs.ebp; buffer += 4; *((u32_t *) buffer) = regs->regs.esi; buffer += 4; *((u32_t *) buffer) = regs->regs.edi; buffer += 4; *((u32_t *) buffer) = (u32_t) regs->regs.eip; buffer += 4; *((u32_t *) buffer) = regs->regs.eflags; buffer += 4; *((u32_t *) buffer) = regs->regs.cs; } /** * @brief Write given registers buffer to GDB register set * * This routine fills given register set with value from provided buffer. * The provided buffer must be large enough to contain all register values. * It is up to the caller to do this check. * * @param regs Destination GDB register set to fill * @param esf Source buffer * * @return N/A */ void gdb_arch_regs_set(struct gdb_reg_set *regs, char *buffer) { regs->regs.eax = *((u32_t *)buffer); buffer += 4; regs->regs.ecx = *((u32_t *)buffer); buffer += 4; regs->regs.edx = *((u32_t *)buffer); buffer += 4; regs->regs.ebx = *((u32_t *)buffer); buffer += 4; regs->regs.esp = *((u32_t *)buffer); buffer += 4; regs->regs.ebp = *((u32_t *)buffer); buffer += 4; regs->regs.esi = *((u32_t *)buffer); buffer += 4; regs->regs.edi = *((u32_t *)buffer); buffer += 4; regs->regs.eip = *((u32_t *)buffer); buffer += 4; regs->regs.eflags = *((u32_t *)buffer); buffer += 4; regs->regs.cs = *((u32_t *)buffer); } /** * @brief Get size and offset of given register * * This routine returns size and offset of given register. * * @param reg_id Register identifier * @param size Container to return size of register, in bytes * @param offset Container to return offset of register, in bytes * * @return N/A */ void gdb_arch_reg_info_get(int reg_id, int *size, int *offset) { /* Determine register size and offset */ if (reg_id >= 0 && reg_id < GDB_NUM_REGS) { *size = 4; *offset = 4 * reg_id; } } #ifdef GDB_ARCH_HAS_RUNCONTROL #ifdef GDB_ARCH_HAS_HW_BP /** * @brief Get the HW breakpoint architecture type for a common GDB type * * This routine gets the specific architecture value that corresponds to a * common hardware breakpoint type. * * The values accepted for the @a type are GDB_HW_INST_BP, * GDB_HW_DATA_WRITE_BP, GDB_HW_DATA_ACCESS_BP and GDB_HW_DATA_READ_BP. * * @param type Common GDB breakpoint type * @param len Data length * @param err Error code on failure (return value of -1) * * @return The architecture type, -1 on failure */ static char gdb_hw_bp_type_get(enum gdb_bp_type type, int len, enum gdb_error_code *err) { char hw_type = -1; switch (type) { /* Following combinations are supported on IA */ case GDB_HW_INST_BP: hw_type = 0; break; case GDB_HW_DATA_WRITE_BP: if (len == 1) { hw_type = 0x1; } else if (len == 2) { hw_type = 0x5; } else if (len == 4) { hw_type = 0xd; } else if (len == 8) { hw_type = 0x9; } break; case GDB_HW_DATA_ACCESS_BP: if (len == 1) { hw_type = 0x3; } else if (len == 2) { hw_type = 0x7; } else if (len == 4) { hw_type = 0xf; } else if (len == 8) { hw_type = 0xb; } break; case GDB_HW_DATA_READ_BP: /* Data read not supported on IA */ /* * NOTE: Read only watchpoints are not supported by IA debug * registers, but it could be possible to use RW watchpoints * and ignore the RW watchpoint if it has been hit by a write * operation. */ *err = GDB_ERROR_HW_BP_NOT_SUP; return -1; default: /* Unknown type */ *err = GDB_ERROR_HW_BP_INVALID_TYPE; return -1; } return hw_type; } /** * @brief Set the debug registers for a specific HW BP. * * This routine sets the @a regs debug registers according to the HW breakpoint * description. * * @param regs Debug registers to set * @param addr Address of the breakpoint * @param type Common GDB breakpoint type * @param len Data length * @param err Error code on failure (return value of -1) * * @return 0 if debug registers have been modified, -1 on error */ int gdb_hw_bp_set(struct gdb_debug_regs *regs, long addr, enum gdb_bp_type type, int len, enum gdb_error_code *err) { char hw_type; hw_type = gdb_hw_bp_type_get(type, len, err); if (hw_type < 0) { return -1; } if (regs->db0 == 0) { regs->db0 = addr; regs->db7 |= (hw_type << 16) | 0x02; } else if (regs->db1 == 0) { regs->db1 = addr; regs->db7 |= (hw_type << 20) | 0x08; } else if (regs->db2 == 0) { regs->db2 = addr; regs->db7 |= (hw_type << 24) | 0x20; } else if (regs->db3 == 0) { regs->db3 = addr; regs->db7 |= (hw_type << 28) | 0x80; } else { *err = GDB_ERROR_HW_BP_DBG_REGS_FULL; return -1; } /* set GE bit if it is data breakpoint */ if (hw_type != 0) { regs->db7 |= 0x200; } return 0; } /** * @brief Clear the debug registers for a specific HW BP. * * This routine updates the @a regs debug registers to remove a HW breakpoint. * * @param regs Debug registers to clear * @param addr Address of the breakpoint * @param type Common GDB breakpoint type * @param len Data length * @param err Error code on failure (return value of -1) * * @return 0 if debug registers have been modified, -1 on error */ int gdb_hw_bp_clear(struct gdb_debug_regs *regs, long addr, enum gdb_bp_type type, int len, enum gdb_error_code *err) { char hw_type; hw_type = gdb_hw_bp_type_get(type, len, err); if (hw_type < 0) { return -1; } if ((regs->db0 == addr) && (((regs->db7 >> 16) & 0xf) == hw_type)) { regs->db0 = 0; regs->db7 &= ~((hw_type << 16) | 0x02); } else if ((regs->db1 == addr) && (((regs->db7 >> 20) & 0xf) == hw_type)) { regs->db1 = 0; regs->db7 &= ~((hw_type << 20) | 0x08); } else if ((regs->db2 == addr) && (((regs->db7 >> 24) & 0xf) == hw_type)) { regs->db2 = 0; regs->db7 &= ~((hw_type << 24) | 0x20); } else if ((regs->db3 == addr) && (((regs->db7 >> 28) & 0xf) == hw_type)) { regs->db3 = 0; regs->db7 &= ~((hw_type << 28) | 0x80); } else { /* Unknown breakpoint */ *err = GDB_ERROR_INVALID_BP; return -1; } return 0; } /** * @brief Look for a Hardware breakpoint * * This routine checks from the @a regs debug register set if a hardware * breakpoint has been hit. * * @param regs Debug registers to check * @param bp_type Common GDB breakpoint type * @param address Address of the breakpoint * * @return 0 if a HW BP has been found, -1 otherwise */ static int gdb_hw_bp_find(struct gdb_debug_regs *regs, enum gdb_bp_type *bp_type, long *address) { int ix; unsigned char type = 0; long addr = 0; int status_bit; int enable_bit; /* get address and type of breakpoint from DR6 and DR7 */ for (ix = 0; ix < 4; ix++) { status_bit = 1 << ix; enable_bit = 2 << (ix << 1); if ((regs->db6 & status_bit) && (regs->db7 & enable_bit)) { switch (ix) { case 0: addr = regs->db0; type = (regs->db7 & 0x000f0000) >> 16; break; case 1: addr = regs->db1; type = (regs->db7 & 0x00f00000) >> 20; break; case 2: addr = regs->db2; type = (regs->db7 & 0x0f000000) >> 24; break; case 3: addr = regs->db3; type = (regs->db7 & 0xf0000000) >> 28; break; } } } if ((addr == 0) && (type == 0)) return -1; *address = addr; switch (type) { case 0x1: case 0x5: case 0xd: case 0x9: *bp_type = GDB_HW_DATA_WRITE_BP; break; case 0x3: case 0x7: case 0xf: case 0xb: *bp_type = GDB_HW_DATA_ACCESS_BP; break; default: *bp_type = GDB_HW_INST_BP; break; } return 0; } /** * @brief Clear all debug registers. * * This routine clears all debug registers * * @return N/A */ void gdb_dbg_regs_clear(void) { struct gdb_debug_regs regs; regs.db0 = 0; regs.db1 = 0; regs.db2 = 0; regs.db3 = 0; regs.db6 = 0; regs.db7 = 0; gdb_dbg_regs_set(®s); } #endif /* GDB_ARCH_HAS_HW_BP */ /** * @brief Clear trace mode * * This routine makes CPU trace-disabled. * * @param regs GDB register set to modify. * @param arg Interrupt locking key * * @return N/A */ void gdb_trace_mode_clear(struct gdb_reg_set *regs, int arg) { regs->regs.eflags &= ~INT_FLAG; regs->regs.eflags |= (arg & INT_FLAG); regs->regs.eflags &= ~TRACE_FLAG; } /** * @brief Test if single stepping is possible for current program counter * * @param regs GDB register set to fetch PC from. * * @return 1 if it is possible to step the instruction, 0 otherwise */ int gdb_arch_can_step(struct gdb_reg_set *regs) { unsigned char *pc = (unsigned char *)regs->regs.eip; if (*pc == INSTRUCTION_HLT) { return 0; } return 1; } /** * @brief Set trace mode * * This routine makes CPU trace-enabled. * * In the event that the program counter currently points to a sti or a cli * instruction, the returned eflags will contain an IF bit as if that * instruction had executed (set for sti, cleared for cli). * * @param regs GDB register set to modify. * * @return eflags with IF bit possibly modified by current sti/cli instruction. */ int gdb_trace_mode_set(struct gdb_reg_set *regs) { unsigned char *pc = (unsigned char *)regs->regs.eip; int simulated_eflags = regs->regs.eflags; if (*pc == INSTRUCTION_STI) { simulated_eflags |= INT_FLAG; } if (*pc == INSTRUCTION_CLI) { simulated_eflags &= ~INT_FLAG; } regs->regs.eflags &= ~INT_FLAG; regs->regs.eflags |= TRACE_FLAG; return simulated_eflags; } #ifdef GDB_ARCH_HAS_HW_BP /** * @brief Implementation of GDB trace handler for HW breakpoint support * * @param esf Exception stack frame when taking the exception * * @return N/A */ static void _do_gdb_trace_handler(NANO_ESF *esf) { struct gdb_debug_regs regs; gdb_dbg_regs_get(®s); if ((regs.db6 & 0x00004000) == 0x00004000) { gdb_handler(GDB_EXC_TRACE, esf, GDB_SIG_TRAP); } else { int type; long addr; gdb_dbg_regs_clear(); (void)gdb_hw_bp_find(®s, &type, &addr); gdb_cpu_stop_hw_bp_addr = addr; gdb_cpu_stop_bp_type = type; gdb_debug_status = DEBUGGING; gdb_handler(GDB_EXC_BP, esf, GDB_SIG_TRAP); } } #else /** * @brief Implementation of GDB trace handler for SW breakpoint support * * @param esf Exception stack frame when taking the exception * * @return N/A */ static void _do_gdb_trace_handler(NANO_ESF *esf) { gdb_handler(GDB_EXC_TRACE, esf, GDB_SIG_TRAP); } #endif /** * @brief GDB trace handler * * The GDB trace handler is used to catch and handle the trace mode exceptions * (single step). * * @param esf Exception stack frame when taking the exception * * @return N/A */ void gdb_trace_handler(NANO_ESF *esf) { (void)irq_lock(); _do_gdb_trace_handler(esf); } _EXCEPTION_CONNECT_NOCODE(gdb_trace_handler, IV_DEBUG); /** * @brief GDB breakpoint handler * * The GDB breakpoint handler is used to catch and handle the breakpoint * exceptions. * * @param esf Exception stack frame when taking the exception * * @return N/A */ void gdb_bp_handler(NANO_ESF *esf) { (void)irq_lock(); gdb_debug_status = DEBUGGING; GDB_SET_STOP_BP_TYPE_SOFT(GDB_SOFT_BP); esf->eip -= sizeof(gdb_instr_t); gdb_handler(GDB_EXC_BP, esf, GDB_SIG_TRAP); } _EXCEPTION_CONNECT_NOCODE(gdb_bp_handler, IV_BREAKPOINT); /** * @brief GDB division-by-zero handler * * This GDB handler is used to catch and handle the division-by-zero exception. * * @param esf Exception stack frame when taking the exception * * @return N/A */ void gdb_div_by_zero_handler(NANO_ESF *esf) { (void)irq_lock(); gdb_debug_status = DEBUGGING; gdb_handler(GDB_EXC_OTHER, esf, GDB_SIG_FPE); } _EXCEPTION_CONNECT_NOCODE(gdb_div_by_zero_handler, IV_DIVIDE_ERROR); /** * @brief GDB page fault handler * * This GDB handler is used to catch and handle the page fault exceptions. * * @param esf Exception stack frame when taking the exception * * @return N/A */ void gdb_pfault_handler(NANO_ESF *esf) { (void)irq_lock(); gdb_debug_status = DEBUGGING; gdb_handler(GDB_EXC_OTHER, esf, GDB_SIG_SIGSEGV); } _EXCEPTION_CONNECT_CODE(gdb_pfault_handler, IV_PAGE_FAULT); #endif /* GDB_ARCH_HAS_RUNCONTROL */