|  | /* | 
|  | * 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 */ |