zephyr/arch/x86/core/ia32/gdbstub.c

261 lines
6.2 KiB
C

/*
* Copyright (c) 2020 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <kernel_internal.h>
#include <ia32/exception.h>
#include <inttypes.h>
#include <zephyr/debug/gdbstub.h>
static struct gdb_ctx debug_ctx;
/**
* Currently we just handle vectors 1 and 3 but lets keep it generic
* to be able to notify other exceptions in the future
*/
static unsigned int get_exception(unsigned int vector)
{
unsigned int exception;
switch (vector) {
case IV_DIVIDE_ERROR:
exception = GDB_EXCEPTION_DIVIDE_ERROR;
break;
case IV_DEBUG:
exception = GDB_EXCEPTION_BREAKPOINT;
break;
case IV_BREAKPOINT:
exception = GDB_EXCEPTION_BREAKPOINT;
break;
case IV_OVERFLOW:
exception = GDB_EXCEPTION_OVERFLOW;
break;
case IV_BOUND_RANGE:
exception = GDB_EXCEPTION_OVERFLOW;
break;
case IV_INVALID_OPCODE:
exception = GDB_EXCEPTION_INVALID_INSTRUCTION;
break;
case IV_DEVICE_NOT_AVAILABLE:
exception = GDB_EXCEPTION_DIVIDE_ERROR;
break;
case IV_DOUBLE_FAULT:
exception = GDB_EXCEPTION_MEMORY_FAULT;
break;
case IV_COPROC_SEGMENT_OVERRUN:
exception = GDB_EXCEPTION_INVALID_MEMORY;
break;
case IV_INVALID_TSS:
exception = GDB_EXCEPTION_INVALID_MEMORY;
break;
case IV_SEGMENT_NOT_PRESENT:
exception = GDB_EXCEPTION_INVALID_MEMORY;
break;
case IV_STACK_FAULT:
exception = GDB_EXCEPTION_INVALID_MEMORY;
break;
case IV_GENERAL_PROTECTION:
exception = GDB_EXCEPTION_INVALID_MEMORY;
break;
case IV_PAGE_FAULT:
exception = GDB_EXCEPTION_INVALID_MEMORY;
break;
case IV_X87_FPU_FP_ERROR:
exception = GDB_EXCEPTION_MEMORY_FAULT;
break;
default:
exception = GDB_EXCEPTION_MEMORY_FAULT;
break;
}
return exception;
}
/*
* Debug exception handler.
*/
static void z_gdb_interrupt(unsigned int vector, z_arch_esf_t *esf)
{
debug_ctx.exception = get_exception(vector);
debug_ctx.registers[GDB_EAX] = esf->eax;
debug_ctx.registers[GDB_ECX] = esf->ecx;
debug_ctx.registers[GDB_EDX] = esf->edx;
debug_ctx.registers[GDB_EBX] = esf->ebx;
debug_ctx.registers[GDB_ESP] = esf->esp;
debug_ctx.registers[GDB_EBP] = esf->ebp;
debug_ctx.registers[GDB_ESI] = esf->esi;
debug_ctx.registers[GDB_EDI] = esf->edi;
debug_ctx.registers[GDB_PC] = esf->eip;
debug_ctx.registers[GDB_CS] = esf->cs;
debug_ctx.registers[GDB_EFLAGS] = esf->eflags;
debug_ctx.registers[GDB_SS] = esf->ss;
debug_ctx.registers[GDB_DS] = esf->ds;
debug_ctx.registers[GDB_ES] = esf->es;
debug_ctx.registers[GDB_FS] = esf->fs;
debug_ctx.registers[GDB_GS] = esf->gs;
z_gdb_main_loop(&debug_ctx);
esf->eax = debug_ctx.registers[GDB_EAX];
esf->ecx = debug_ctx.registers[GDB_ECX];
esf->edx = debug_ctx.registers[GDB_EDX];
esf->ebx = debug_ctx.registers[GDB_EBX];
esf->esp = debug_ctx.registers[GDB_ESP];
esf->ebp = debug_ctx.registers[GDB_EBP];
esf->esi = debug_ctx.registers[GDB_ESI];
esf->edi = debug_ctx.registers[GDB_EDI];
esf->eip = debug_ctx.registers[GDB_PC];
esf->cs = debug_ctx.registers[GDB_CS];
esf->eflags = debug_ctx.registers[GDB_EFLAGS];
esf->ss = debug_ctx.registers[GDB_SS];
esf->ds = debug_ctx.registers[GDB_DS];
esf->es = debug_ctx.registers[GDB_ES];
esf->fs = debug_ctx.registers[GDB_FS];
esf->gs = debug_ctx.registers[GDB_GS];
}
void arch_gdb_continue(void)
{
/* Clear the TRAP FLAG bit */
debug_ctx.registers[GDB_EFLAGS] &= ~BIT(8);
}
void arch_gdb_step(void)
{
/* Set the TRAP FLAG bit */
debug_ctx.registers[GDB_EFLAGS] |= BIT(8);
}
size_t arch_gdb_reg_readall(struct gdb_ctx *ctx, uint8_t *buf, size_t buflen)
{
size_t ret;
if (buflen < (sizeof(ctx->registers) * 2)) {
ret = 0;
} else {
ret = bin2hex((const uint8_t *)&(ctx->registers),
sizeof(ctx->registers), buf, buflen);
}
return ret;
}
size_t arch_gdb_reg_writeall(struct gdb_ctx *ctx, uint8_t *hex, size_t hexlen)
{
size_t ret;
if (hexlen != (sizeof(ctx->registers) * 2)) {
ret = 0;
} else {
ret = hex2bin(hex, hexlen,
(uint8_t *)&(ctx->registers),
sizeof(ctx->registers));
}
return ret;
}
size_t arch_gdb_reg_readone(struct gdb_ctx *ctx, uint8_t *buf, size_t buflen,
uint32_t regno)
{
size_t ret;
if (buflen < (sizeof(unsigned int) * 2)) {
/* Make sure there is enough space to write hex string */
ret = 0;
} else if (regno >= GDB_STUB_NUM_REGISTERS) {
/* Return hex string "xx" to tell GDB that this register
* is not available. So GDB will continue probing other
* registers instead of stopping in the middle of
* "info registers all".
*/
if (buflen >= 2) {
memcpy(buf, "xx", 2);
ret = 2;
} else {
ret = 0;
}
} else {
ret = bin2hex((const uint8_t *)&(ctx->registers[regno]),
sizeof(ctx->registers[regno]),
buf, buflen);
}
return ret;
}
size_t arch_gdb_reg_writeone(struct gdb_ctx *ctx, uint8_t *hex, size_t hexlen,
uint32_t regno)
{
size_t ret;
if (regno == GDB_ORIG_EAX) {
/* GDB requires orig_eax that seems to be
* Linux specific. Unfortunately if we just
* return error, GDB will stop working.
* So just fake an OK response by saying
* that we have processed the hex string.
*/
ret = hexlen;
} else if (regno >= GDB_STUB_NUM_REGISTERS) {
ret = 0;
} else if (hexlen != (sizeof(unsigned int) * 2)) {
/* Make sure the input hex string matches register size */
ret = 0;
} else {
ret = hex2bin(hex, hexlen,
(uint8_t *)&(ctx->registers[regno]),
sizeof(ctx->registers[regno]));
}
return ret;
}
static __used void z_gdb_debug_isr(z_arch_esf_t *esf)
{
#ifdef CONFIG_GDBSTUB_TRACE
printk("gdbstub:enter %s (IV_DEBUG)\n", __func__);
#endif
z_gdb_interrupt(IV_DEBUG, esf);
#ifdef CONFIG_GDBSTUB_TRACE
printk("gdbstub:exit %s (IV_DEBUG)\n", __func__);
#endif
}
static __used void z_gdb_break_isr(z_arch_esf_t *esf)
{
#ifdef CONFIG_GDBSTUB_TRACE
printk("gdbstub:enter %s (IV_BREAKPOINT)\n", __func__);
#endif
z_gdb_interrupt(IV_BREAKPOINT, esf);
#ifdef CONFIG_GDBSTUB_TRACE
printk("gdbstub:exit %s (IV_BREAKPOINT)\n", __func__);
#endif
}
void arch_gdb_init(void)
{
#ifdef CONFIG_GDBSTUB_TRACE
printk("gdbstub:%s awaits GDB connection\n", __func__);
#endif
__asm__ volatile ("int3");
#ifdef CONFIG_GDBSTUB_TRACE
printk("gdbstub:%s GDB is connected\n", __func__);
#endif
}
/* Hook current IDT. */
_EXCEPTION_CONNECT_NOCODE(z_gdb_debug_isr, IV_DEBUG, 3);
_EXCEPTION_CONNECT_NOCODE(z_gdb_break_isr, IV_BREAKPOINT, 3);