debug: x86: Add gdbstub for X86
It implements gdb remote protocol to talk with a host gdb during the debug session. The implementation is divided in three layers: 1 - The top layer that is responsible for the gdb remote protocol. 2 - An architecture specific layer responsible to write/read registers, set breakpoints, handle exceptions, ... 3 - A transport layer to be used to communicate with the host The communication with GDB in the host is synchronous and the systems stops execution waiting for instructions and return its execution after a "continue" or "step" command. The protocol has an exception that is when the host sends a packet to cause an interruption, usually triggered by a Ctrl-C. This implementation ignores this instruction though. This initial work supports only X86 using uart as backend. Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>
This commit is contained in:
parent
ffc1da08f9
commit
5408f3102d
15 changed files with 883 additions and 2 deletions
|
@ -44,6 +44,7 @@ config X86
|
|||
select ARCH_SUPPORTS_COREDUMP
|
||||
select CPU_HAS_MMU
|
||||
select ARCH_MEM_DOMAIN_SYNCHRONOUS_API if USERSPACE
|
||||
select ARCH_HAS_GDBSTUB if !X86_64
|
||||
help
|
||||
x86 architecture
|
||||
|
||||
|
@ -411,6 +412,9 @@ config ARCH_SUPPORTS_COREDUMP
|
|||
config ARCH_HAS_EXTRA_EXCEPTION_INFO
|
||||
bool
|
||||
|
||||
config ARCH_HAS_GDBSTUB
|
||||
bool
|
||||
|
||||
#
|
||||
# Other architecture related options
|
||||
#
|
||||
|
|
|
@ -22,6 +22,7 @@ zephyr_library_sources(
|
|||
zephyr_library_sources_ifdef(CONFIG_IRQ_OFFLOAD ia32/irq_offload.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_X86_USERSPACE ia32/userspace.S)
|
||||
zephyr_library_sources_ifdef(CONFIG_LAZY_FPU_SHARING ia32/float.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_GDBSTUB ia32/gdbstub.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_DEBUG_COREDUMP ia32/coredump.c)
|
||||
|
||||
|
|
|
@ -121,6 +121,14 @@ SECTION_FUNC(TEXT, _exception_enter)
|
|||
pushl %eax /* Save calculated ESP */
|
||||
#ifdef CONFIG_USERSPACE
|
||||
2:
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GDBSTUB
|
||||
pushl %ds
|
||||
pushl %es
|
||||
pushl %fs
|
||||
pushl %gs
|
||||
pushl %ss
|
||||
#endif
|
||||
/* ESP is pointing to the ESF at this point */
|
||||
|
||||
|
@ -196,6 +204,13 @@ allDone:
|
|||
nestedException:
|
||||
#endif /* CONFIG_LAZY_FPU_SHARING */
|
||||
|
||||
#ifdef CONFIG_GDBSTUB
|
||||
popl %ss
|
||||
popl %gs
|
||||
popl %fs
|
||||
popl %es
|
||||
popl %ds
|
||||
#endif
|
||||
/*
|
||||
* Pop the non-volatile registers from the stack.
|
||||
* Note that debug tools may have altered the saved register values while
|
||||
|
|
154
arch/x86/core/ia32/gdbstub.c
Normal file
154
arch/x86/core/ia32/gdbstub.c
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <kernel.h>
|
||||
#include <kernel_internal.h>
|
||||
#include <ia32/exception.h>
|
||||
#include <inttypes.h>
|
||||
#include <debug/gdbstub.h>
|
||||
|
||||
|
||||
static struct gdb_ctx ctx;
|
||||
static bool start;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
ctx.exception = get_exception(vector);
|
||||
|
||||
ctx.registers[GDB_EAX] = esf->eax;
|
||||
ctx.registers[GDB_ECX] = esf->ecx;
|
||||
ctx.registers[GDB_EDX] = esf->edx;
|
||||
ctx.registers[GDB_EBX] = esf->ebx;
|
||||
ctx.registers[GDB_ESP] = esf->esp;
|
||||
ctx.registers[GDB_EBP] = esf->ebp;
|
||||
ctx.registers[GDB_ESI] = esf->esi;
|
||||
ctx.registers[GDB_EDI] = esf->edi;
|
||||
ctx.registers[GDB_PC] = esf->eip;
|
||||
ctx.registers[GDB_CS] = esf->cs;
|
||||
ctx.registers[GDB_EFLAGS] = esf->eflags;
|
||||
ctx.registers[GDB_SS] = esf->ss;
|
||||
ctx.registers[GDB_DS] = esf->ds;
|
||||
ctx.registers[GDB_ES] = esf->es;
|
||||
ctx.registers[GDB_FS] = esf->fs;
|
||||
ctx.registers[GDB_GS] = esf->gs;
|
||||
|
||||
z_gdb_main_loop(&ctx, start);
|
||||
start = false;
|
||||
|
||||
esf->eax = ctx.registers[GDB_EAX];
|
||||
esf->ecx = ctx.registers[GDB_ECX];
|
||||
esf->edx = ctx.registers[GDB_EDX];
|
||||
esf->ebx = ctx.registers[GDB_EBX];
|
||||
esf->esp = ctx.registers[GDB_ESP];
|
||||
esf->ebp = ctx.registers[GDB_EBP];
|
||||
esf->esi = ctx.registers[GDB_ESI];
|
||||
esf->edi = ctx.registers[GDB_EDI];
|
||||
esf->eip = ctx.registers[GDB_PC];
|
||||
esf->cs = ctx.registers[GDB_CS];
|
||||
esf->eflags = ctx.registers[GDB_EFLAGS];
|
||||
esf->ss = ctx.registers[GDB_SS];
|
||||
esf->ds = ctx.registers[GDB_DS];
|
||||
esf->es = ctx.registers[GDB_ES];
|
||||
esf->fs = ctx.registers[GDB_FS];
|
||||
esf->gs = ctx.registers[GDB_GS];
|
||||
}
|
||||
|
||||
void arch_gdb_continue(void)
|
||||
{
|
||||
/* Clear the TRAP FLAG bit */
|
||||
ctx.registers[GDB_EFLAGS] &= ~BIT(8);
|
||||
}
|
||||
|
||||
void arch_gdb_step(void)
|
||||
{
|
||||
/* Set the TRAP FLAG bit */
|
||||
ctx.registers[GDB_EFLAGS] |= BIT(8);
|
||||
}
|
||||
|
||||
static __used void z_gdb_debug_isr(z_arch_esf_t *esf)
|
||||
{
|
||||
z_gdb_interrupt(IV_DEBUG, esf);
|
||||
}
|
||||
|
||||
static __used void z_gdb_break_isr(z_arch_esf_t *esf)
|
||||
{
|
||||
z_gdb_interrupt(IV_BREAKPOINT, esf);
|
||||
}
|
||||
|
||||
void arch_gdb_init(void)
|
||||
{
|
||||
start = true;
|
||||
__asm__ volatile ("int3");
|
||||
}
|
||||
|
||||
/* Hook current IDT. */
|
||||
_EXCEPTION_CONNECT_NOCODE(z_gdb_debug_isr, IV_DEBUG);
|
||||
_EXCEPTION_CONNECT_NOCODE(z_gdb_break_isr, IV_BREAKPOINT);
|
|
@ -521,10 +521,14 @@ def main():
|
|||
if is_perm_regions:
|
||||
# Need to accomplish the following things:
|
||||
# - Text regions need the XD flag cleared and RW flag removed
|
||||
# if not built with gdbstub support
|
||||
# - Rodata regions need the RW flag cleared
|
||||
# - User mode needs access as we currently do not separate application
|
||||
# text/rodata from kernel text/rodata
|
||||
pt.set_region_perms("_image_text", FLAG_P | FLAG_US)
|
||||
if isdef("CONFIG_GDBSTUB"):
|
||||
pt.set_region_perms("_image_text", FLAG_P | FLAG_US | FLAG_RW)
|
||||
else:
|
||||
pt.set_region_perms("_image_text", FLAG_P | FLAG_US)
|
||||
pt.set_region_perms("_image_rodata", FLAG_P | FLAG_US | FLAG_XD)
|
||||
|
||||
if isdef("CONFIG_COVERAGE_GCOV") and isdef("CONFIG_USERSPACE"):
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <kernel_structs.h>
|
||||
#include <arch/common/ffs.h>
|
||||
#include <sys/util.h>
|
||||
#include <arch/x86/ia32/gdbstub.h>
|
||||
#include <arch/x86/ia32/thread.h>
|
||||
#include <arch/x86/ia32/syscall.h>
|
||||
|
||||
|
@ -329,6 +330,13 @@ static inline void arch_isr_direct_footer(int swap)
|
|||
*/
|
||||
|
||||
typedef struct nanoEsf {
|
||||
#ifdef CONFIG_GDBSTUB
|
||||
unsigned int ss;
|
||||
unsigned int gs;
|
||||
unsigned int fs;
|
||||
unsigned int es;
|
||||
unsigned int ds;
|
||||
#endif
|
||||
unsigned int esp;
|
||||
unsigned int ebp;
|
||||
unsigned int ebx;
|
||||
|
|
84
include/arch/x86/ia32/gdbstub.h
Normal file
84
include/arch/x86/ia32/gdbstub.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief IA-32 specific gdbstub interface header
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_ARCH_X86_GDBSTUB_SYS_H_
|
||||
#define ZEPHYR_INCLUDE_ARCH_X86_GDBSTUB_SYS_H_
|
||||
|
||||
#ifndef _ASMLANGUAGE
|
||||
|
||||
#include <stdint.h>
|
||||
#include <toolchain.h>
|
||||
|
||||
/**
|
||||
* @brief Number of register used by gdbstub in IA-32
|
||||
*/
|
||||
#define ARCH_GDB_NUM_REGISTERS 16
|
||||
|
||||
/**
|
||||
* @brief GDB interruption context
|
||||
*
|
||||
* The exception stack frame contents used by gdbstub. The contents
|
||||
* of this struct are used to display information about the current
|
||||
* cpu state.
|
||||
*/
|
||||
|
||||
struct gdb_interrupt_ctx {
|
||||
uint32_t ss;
|
||||
uint32_t gs;
|
||||
uint32_t fs;
|
||||
uint32_t es;
|
||||
uint32_t ds;
|
||||
uint32_t edi;
|
||||
uint32_t esi;
|
||||
uint32_t ebp;
|
||||
uint32_t esp;
|
||||
uint32_t ebx;
|
||||
uint32_t edx;
|
||||
uint32_t ecx;
|
||||
uint32_t eax;
|
||||
uint32_t vector;
|
||||
uint32_t error_code;
|
||||
uint32_t eip;
|
||||
uint32_t cs;
|
||||
uint32_t eflags;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* @brief IA-32 register used in gdbstub
|
||||
*/
|
||||
|
||||
enum GDB_REGISTER {
|
||||
GDB_EAX,
|
||||
GDB_ECX,
|
||||
GDB_EDX,
|
||||
GDB_EBX,
|
||||
GDB_ESP,
|
||||
GDB_EBP,
|
||||
GDB_ESI,
|
||||
GDB_EDI,
|
||||
GDB_PC,
|
||||
GDB_EFLAGS,
|
||||
GDB_CS,
|
||||
GDB_SS,
|
||||
GDB_DS,
|
||||
GDB_ES,
|
||||
GDB_FS,
|
||||
GDB_GS
|
||||
};
|
||||
|
||||
struct gdb_ctx {
|
||||
unsigned int exception;
|
||||
unsigned int registers[ARCH_GDB_NUM_REGISTERS];
|
||||
};
|
||||
|
||||
#endif /* _ASMLANGUAGE */
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_ARCH_X86_GDBSTUB_SYS_H_ */
|
18
include/debug/gdbstub.h
Normal file
18
include/debug/gdbstub.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_DEBUG_GDBSTUB_H_
|
||||
#define ZEPHYR_INCLUDE_DEBUG_GDBSTUB_H_
|
||||
|
||||
/* Map from CPU excpetions to GDB */
|
||||
#define GDB_EXCEPTION_INVALID_INSTRUCTION 4UL
|
||||
#define GDB_EXCEPTION_BREAKPOINT 5UL
|
||||
#define GDB_EXCEPTION_MEMORY_FAULT 7UL
|
||||
#define GDB_EXCEPTION_DIVIDE_ERROR 8UL
|
||||
#define GDB_EXCEPTION_INVALID_MEMORY 11UL
|
||||
#define GDB_EXCEPTION_OVERFLOW 16UL
|
||||
|
||||
#endif
|
|
@ -714,6 +714,44 @@ extern uint64_t arch_timing_value_swap_temp;
|
|||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup arch-gdbstub Architecture-specific gdbstub APIs
|
||||
* @ingroup arch-interface
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @def ARCH_GDB_NUM_REGISTERS
|
||||
*
|
||||
* ARCH_GDB_NUM_REGISTERS is architecure specific and
|
||||
* this symbol must be defined in architecure specific header
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_GDBSTUB
|
||||
/**
|
||||
* @brief Architecture layer debug start
|
||||
*
|
||||
* This function is called by @c gdb_init()
|
||||
*/
|
||||
void arch_gdb_init(void);
|
||||
|
||||
/**
|
||||
* @brief Continue running program
|
||||
*
|
||||
* Continue software execution.
|
||||
*/
|
||||
void arch_gdb_continue(void);
|
||||
|
||||
/**
|
||||
* @brief Continue with one step
|
||||
*
|
||||
* Continue software execution until reaches the next statement.
|
||||
*/
|
||||
void arch_gdb_step(void);
|
||||
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup arch_cache Architecture-specific cache functions
|
||||
* @ingroup arch-interface
|
||||
|
|
|
@ -128,6 +128,16 @@ extern uint8_t *z_priv_stack_find(k_thread_stack_t *stack);
|
|||
bool z_stack_is_user_capable(k_thread_stack_t *stack);
|
||||
#endif /* CONFIG_USERSPACE */
|
||||
|
||||
#ifdef CONFIG_GDBSTUB
|
||||
struct gdb_ctx;
|
||||
|
||||
/* Should be called by the arch layer. This is the gdbstub main loop
|
||||
* and synchronously communicate with gdb on host.
|
||||
*/
|
||||
extern int z_gdb_main_loop(struct gdb_ctx *ctx, bool start);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -19,3 +19,13 @@ add_subdirectory_ifdef(
|
|||
CONFIG_DEBUG_COREDUMP
|
||||
coredump
|
||||
)
|
||||
|
||||
zephyr_sources_ifdef(
|
||||
CONFIG_GDBSTUB
|
||||
gdbstub.c
|
||||
)
|
||||
|
||||
zephyr_sources_ifdef(
|
||||
CONFIG_GDBSTUB_SERIAL_BACKEND
|
||||
gdbstub/gdbstub_backend_serial.c
|
||||
)
|
||||
|
|
|
@ -343,5 +343,34 @@ config OPENOCD_SUPPORT
|
|||
selects CONFIG_THREAD_MONITOR, so all of its caveats are implied.)
|
||||
|
||||
rsource "coredump/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
||||
config GDBSTUB
|
||||
bool "GDB remote serial protocol support [EXPERIMENTAL]"
|
||||
depends on ARCH_HAS_GDBSTUB
|
||||
help
|
||||
This option enable support the target using GDB, or any other
|
||||
application that supports GDB protocol.
|
||||
|
||||
if GDBSTUB
|
||||
|
||||
choice
|
||||
prompt "GDB backend"
|
||||
|
||||
config GDBSTUB_SERIAL_BACKEND
|
||||
bool "Use serial backend"
|
||||
depends on SERIAL
|
||||
help
|
||||
Use serial as backenf for GDB
|
||||
|
||||
endchoice
|
||||
|
||||
config GDBSTUB_SERIAL_BACKEND_NAME
|
||||
string "Serial Name"
|
||||
depends on GDBSTUB_SERIAL_BACKEND
|
||||
default "UART_0"
|
||||
help
|
||||
Use serial as backenf for GDB
|
||||
|
||||
|
||||
endif
|
||||
|
|
408
subsys/debug/gdbstub.c
Normal file
408
subsys/debug/gdbstub.c
Normal file
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <device.h>
|
||||
#include <kernel.h>
|
||||
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_REGISTER(gdbstub);
|
||||
|
||||
#include <sys/util.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "gdbstub_backend.h"
|
||||
|
||||
#define GDB_PACKET_SIZE 256
|
||||
|
||||
/* GDB remote serial protocol does not define errors value properly
|
||||
* and handle all error packets as the same the code error is not
|
||||
* used. There are informal values used by others gdbstub
|
||||
* implementation, like qemu. Lets use the same here.
|
||||
*/
|
||||
#define GDB_ERROR_GENERAL "E01"
|
||||
#define GDB_ERROR_MEMORY "E14"
|
||||
#define GDB_ERROR_OVERFLOW "E22"
|
||||
|
||||
/**
|
||||
* Add preamble and termination to the given data.
|
||||
*
|
||||
* It returns 0 if the packet was acknowledge, -1 otherwise.
|
||||
*/
|
||||
static int gdb_send_packet(const uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t buf[2];
|
||||
uint8_t checksum = 0;
|
||||
|
||||
/* Send packet start */
|
||||
z_gdb_putchar('$');
|
||||
|
||||
/* Send packet data and calculate checksum */
|
||||
while (len-- > 0) {
|
||||
checksum += *data;
|
||||
z_gdb_putchar(*data++);
|
||||
}
|
||||
|
||||
/* Send the checksum */
|
||||
z_gdb_putchar('#');
|
||||
|
||||
if (bin2hex(&checksum, 1, buf, sizeof(buf)) == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
z_gdb_putchar(buf[0]);
|
||||
z_gdb_putchar(buf[1]);
|
||||
|
||||
if (z_gdb_getchar() == '+') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Just got an invalid response */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a packet
|
||||
*
|
||||
* Return 0 in case of success, otherwise -1
|
||||
*/
|
||||
static int gdb_get_packet(uint8_t *buf, size_t buf_len, size_t *len)
|
||||
{
|
||||
uint8_t ch = '0';
|
||||
uint8_t expected_checksum, checksum = 0;
|
||||
uint8_t checksum_buf[2];
|
||||
|
||||
/* Wait for packet start */
|
||||
checksum = 0;
|
||||
|
||||
/* wait for the start character, ignore the rest */
|
||||
while (ch != '$') {
|
||||
ch = z_gdb_getchar();
|
||||
}
|
||||
|
||||
*len = 0;
|
||||
/* Read until receive # or the end of the buffer */
|
||||
while (*len < (buf_len - 1)) {
|
||||
ch = z_gdb_getchar();
|
||||
|
||||
if (ch == '#') {
|
||||
break;
|
||||
}
|
||||
|
||||
checksum += ch;
|
||||
buf[*len] = ch;
|
||||
(*len)++;
|
||||
}
|
||||
|
||||
buf[*len] = '\0';
|
||||
|
||||
/* Get checksum now */
|
||||
checksum_buf[0] = z_gdb_getchar();
|
||||
checksum_buf[1] = z_gdb_getchar();
|
||||
|
||||
if (hex2bin(checksum_buf, 2, &expected_checksum, 1) == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify checksum */
|
||||
if (checksum != expected_checksum) {
|
||||
LOG_DBG("Bad checksum. Got 0x%x but was expecting: 0x%x",
|
||||
checksum, expected_checksum);
|
||||
/* NACK packet */
|
||||
z_gdb_putchar('-');
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ACK packet */
|
||||
z_gdb_putchar('+');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from a given memory.
|
||||
*
|
||||
* Return 0 in case of success, otherwise -1
|
||||
*/
|
||||
static int gdb_mem_read(uint8_t *buf, size_t buf_len,
|
||||
uintptr_t addr, size_t len)
|
||||
{
|
||||
uint8_t data;
|
||||
size_t pos, count = 0;
|
||||
|
||||
if (len > buf_len) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Read from system memory */
|
||||
for (pos = 0; pos < len; pos++) {
|
||||
data = *(uint8_t *)(addr + pos);
|
||||
count += bin2hex(&data, 1, buf + count, buf_len - count);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data in a given memory.
|
||||
*
|
||||
* Return 0 in case of success, otherwise -1
|
||||
*/
|
||||
static int gdb_mem_write(const uint8_t *buf, uintptr_t addr,
|
||||
size_t len)
|
||||
{
|
||||
uint8_t data;
|
||||
|
||||
while (len > 0) {
|
||||
size_t ret = hex2bin(buf, 2, &data, sizeof(data));
|
||||
|
||||
if (ret == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*(uint8_t *)addr = data;
|
||||
|
||||
addr++;
|
||||
buf += 2;
|
||||
len--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a exception packet "T <value>"
|
||||
*/
|
||||
static int gdb_send_exception(uint8_t *buf, size_t len, uint8_t exception)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
*buf = 'T';
|
||||
size = bin2hex(&exception, 1, buf + 1, len - 1);
|
||||
if (size == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Related to 'T' */
|
||||
size++;
|
||||
|
||||
return gdb_send_packet(buf, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously communicate with gdb on the host
|
||||
*/
|
||||
int z_gdb_main_loop(struct gdb_ctx *ctx, bool start)
|
||||
{
|
||||
uint8_t buf[GDB_PACKET_SIZE];
|
||||
enum loop_state {
|
||||
RECEIVING,
|
||||
CONTINUE,
|
||||
FAILED
|
||||
} state;
|
||||
|
||||
state = RECEIVING;
|
||||
|
||||
if (start == false) {
|
||||
gdb_send_exception(buf, sizeof(buf), ctx->exception);
|
||||
}
|
||||
|
||||
#define CHECK_FAILURE(condition) \
|
||||
{ \
|
||||
if ((condition)) { \
|
||||
state = FAILED; \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CHECK_SYMBOL(c) \
|
||||
{ \
|
||||
CHECK_FAILURE(ptr == NULL || *ptr != (c)); \
|
||||
ptr++; \
|
||||
}
|
||||
|
||||
#define CHECK_INT(arg) \
|
||||
{ \
|
||||
arg = strtol((const char *)ptr, (char **)&ptr, 16); \
|
||||
CHECK_FAILURE(ptr == NULL); \
|
||||
}
|
||||
|
||||
while (state == RECEIVING) {
|
||||
uint8_t *ptr;
|
||||
size_t data_len, pkt_len;
|
||||
uintptr_t addr;
|
||||
int ret;
|
||||
|
||||
ret = gdb_get_packet(buf, sizeof(buf), &pkt_len);
|
||||
CHECK_FAILURE(ret == -1);
|
||||
|
||||
if (pkt_len == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ptr = buf;
|
||||
|
||||
switch (*ptr++) {
|
||||
|
||||
/**
|
||||
* Read from the memory
|
||||
* Format: m addr,length
|
||||
*/
|
||||
case 'm':
|
||||
CHECK_INT(addr);
|
||||
CHECK_SYMBOL(',');
|
||||
CHECK_INT(data_len);
|
||||
|
||||
/* Read Memory */
|
||||
ret = gdb_mem_read(buf, sizeof(buf), addr, data_len);
|
||||
CHECK_FAILURE(ret == -1);
|
||||
gdb_send_packet(buf, ret);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Write to memory
|
||||
* Format: M addr,length:val
|
||||
*/
|
||||
case 'M':
|
||||
CHECK_INT(addr);
|
||||
CHECK_SYMBOL(',');
|
||||
CHECK_INT(data_len);
|
||||
CHECK_SYMBOL(':');
|
||||
|
||||
/* Write Memory */
|
||||
pkt_len = gdb_mem_write(ptr, addr, data_len);
|
||||
CHECK_FAILURE(pkt_len == -1);
|
||||
gdb_send_packet("OK", 2);
|
||||
break;
|
||||
|
||||
/*
|
||||
* Continue ignoring the optional address
|
||||
* Format: c addr
|
||||
*/
|
||||
case 'c':
|
||||
arch_gdb_continue();
|
||||
state = CONTINUE;
|
||||
break;
|
||||
|
||||
/*
|
||||
* Step one instruction ignoring the optional address
|
||||
* s addr..addr
|
||||
*/
|
||||
case 's':
|
||||
arch_gdb_step();
|
||||
state = CONTINUE;
|
||||
break;
|
||||
|
||||
/*
|
||||
* Read all registers
|
||||
* Format: g
|
||||
*/
|
||||
case 'g':
|
||||
pkt_len = bin2hex((const uint8_t *)&(ctx->registers),
|
||||
sizeof(ctx->registers), buf, sizeof(buf));
|
||||
CHECK_FAILURE(pkt_len == 0);
|
||||
gdb_send_packet(buf, pkt_len);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Write the value of the CPU registers
|
||||
* Fromat: G XX...
|
||||
*/
|
||||
case 'G':
|
||||
pkt_len = hex2bin(ptr, pkt_len - 1,
|
||||
(uint8_t *)&(ctx->registers),
|
||||
sizeof(ctx->registers));
|
||||
CHECK_FAILURE(pkt_len == 0);
|
||||
gdb_send_packet("OK", 2);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Read the value of a register
|
||||
* Format: p n
|
||||
*/
|
||||
case 'p':
|
||||
CHECK_INT(addr);
|
||||
CHECK_FAILURE(addr >= ARCH_GDB_NUM_REGISTERS);
|
||||
|
||||
/* Read Register */
|
||||
pkt_len = bin2hex(
|
||||
(const uint8_t *)&(ctx->registers[addr]),
|
||||
sizeof(ctx->registers[addr]),
|
||||
buf, sizeof(buf));
|
||||
CHECK_FAILURE(pkt_len == 0);
|
||||
gdb_send_packet(buf, pkt_len);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Write data into a specific register
|
||||
* Format: P register=value
|
||||
*/
|
||||
case 'P':
|
||||
CHECK_INT(addr);
|
||||
CHECK_SYMBOL('=');
|
||||
|
||||
/*
|
||||
* GDB requires orig_eax that seems to be
|
||||
* Linux specific. Unfortunately if we just
|
||||
* return "E01" gdb will stop. So, we just
|
||||
* send "OK" and ignore it.
|
||||
*/
|
||||
if (addr < ARCH_GDB_NUM_REGISTERS) {
|
||||
pkt_len = hex2bin(ptr, strlen(ptr),
|
||||
(uint8_t *)&(ctx->registers[addr]),
|
||||
sizeof(ctx->registers[addr]));
|
||||
CHECK_FAILURE(pkt_len == 0);
|
||||
}
|
||||
gdb_send_packet("OK", 2);
|
||||
break;
|
||||
|
||||
|
||||
/* What cause the pause */
|
||||
case '?':
|
||||
gdb_send_exception(buf, sizeof(buf),
|
||||
ctx->exception);
|
||||
break;
|
||||
|
||||
/*
|
||||
* Not supported action
|
||||
*/
|
||||
default:
|
||||
gdb_send_packet(NULL, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == FAILED) {
|
||||
gdb_send_packet(GDB_ERROR_GENERAL, 3);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef CHECK_FAILURE
|
||||
#undef CHECK_INT
|
||||
#undef CHECK_SYMBOL
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gdb_init(const struct device *arg)
|
||||
{
|
||||
ARG_UNUSED(arg);
|
||||
|
||||
if (z_gdb_backend_init() == -1) {
|
||||
LOG_ERR("Could not initialize gdbstub backend.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
arch_gdb_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(gdb_init, PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
50
subsys/debug/gdbstub/gdbstub_backend_serial.c
Normal file
50
subsys/debug/gdbstub/gdbstub_backend_serial.c
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <kernel.h>
|
||||
#include <drivers/uart.h>
|
||||
#include <sys/__assert.h>
|
||||
|
||||
static const struct device *uart_dev;
|
||||
|
||||
int z_gdb_backend_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
static const struct uart_config uart_cfg = {
|
||||
.baudrate = 115200,
|
||||
.parity = UART_CFG_PARITY_NONE,
|
||||
.stop_bits = UART_CFG_STOP_BITS_1,
|
||||
.data_bits = UART_CFG_DATA_BITS_8,
|
||||
.flow_ctrl = UART_CFG_FLOW_CTRL_NONE
|
||||
};
|
||||
|
||||
if (uart_dev == NULL) {
|
||||
uart_dev = device_get_binding(
|
||||
CONFIG_GDBSTUB_SERIAL_BACKEND_NAME);
|
||||
|
||||
__ASSERT(uart_dev != NULL, "Could not get uart device");
|
||||
|
||||
ret = uart_configure(uart_dev, &uart_cfg);
|
||||
__ASSERT(ret == 0, "Could not configure uart device");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void z_gdb_putchar(unsigned char ch)
|
||||
{
|
||||
uart_poll_out(uart_dev, ch);
|
||||
}
|
||||
|
||||
unsigned char z_gdb_getchar(void)
|
||||
{
|
||||
unsigned char ch;
|
||||
|
||||
while (uart_poll_in(uart_dev, &ch) < 0) {
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
48
subsys/debug/gdbstub_backend.h
Normal file
48
subsys/debug/gdbstub_backend.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_SUBSYS_DEBUG_GDBSTUB_BACKEND_H_
|
||||
#define ZEPHYR_SUBSYS_DEBUG_GDBSTUB_BACKEND_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* This is an internal header. These API is intended to be used
|
||||
* exclusively by gdbstub.
|
||||
*
|
||||
* A backend has to implement these three functions knowing that they
|
||||
* will be called in an interruption context.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Initialize the gdbstub backend
|
||||
*
|
||||
* This function is called from @c gdb_start to
|
||||
* give the opportunity to the backend initialize properly.
|
||||
*
|
||||
* @retval 0 In case of success
|
||||
* @retval -1 If the backend was not initialized properly
|
||||
*/
|
||||
int z_gdb_backend_init(void);
|
||||
|
||||
/**
|
||||
* @brief Output a character
|
||||
*
|
||||
* @param ch Character to send
|
||||
*/
|
||||
void z_gdb_putchar(unsigned char ch);
|
||||
|
||||
/**
|
||||
* @brief Receive a character
|
||||
*
|
||||
* This function blocks until have a valid
|
||||
* character to return.
|
||||
*
|
||||
* @return A character
|
||||
*/
|
||||
char z_gdb_getchar(void);
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue