zephyr/arch/x86/debug/gdb_arch.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(&regs->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, &regs->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(&regs);
}
#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(&regs);
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(&regs, &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 */