| /* |
| * Copyright (c) 2015-2016 Wind River Systems, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file |
| * x86 part of the GDB server |
| */ |
| |
| #include <kernel.h> |
| #include <kernel_structs.h> |
| #include <string.h> |
| #include <debug/gdb_arch.h> |
| #include <debug/gdb_server.h> |
| |
| #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 */ |