690 lines
15 KiB
C
690 lines
15 KiB
C
/*
|
|
* 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 */
|