diff --git a/arch/Kconfig b/arch/Kconfig index bfefbcae6ef..3adbd6a65a9 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -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 # diff --git a/arch/x86/core/ia32.cmake b/arch/x86/core/ia32.cmake index 9739555e8db..fab3ff03350 100644 --- a/arch/x86/core/ia32.cmake +++ b/arch/x86/core/ia32.cmake @@ -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) diff --git a/arch/x86/core/ia32/excstub.S b/arch/x86/core/ia32/excstub.S index cefe84cfe09..891c55e3807 100644 --- a/arch/x86/core/ia32/excstub.S +++ b/arch/x86/core/ia32/excstub.S @@ -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 diff --git a/arch/x86/core/ia32/gdbstub.c b/arch/x86/core/ia32/gdbstub.c new file mode 100644 index 00000000000..b17225f7597 --- /dev/null +++ b/arch/x86/core/ia32/gdbstub.c @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + + +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); diff --git a/arch/x86/gen_mmu.py b/arch/x86/gen_mmu.py index 589078dea9d..d17f6f8540b 100755 --- a/arch/x86/gen_mmu.py +++ b/arch/x86/gen_mmu.py @@ -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"): diff --git a/include/arch/x86/ia32/arch.h b/include/arch/x86/ia32/arch.h index f5e07f3538a..5e8c70536df 100644 --- a/include/arch/x86/ia32/arch.h +++ b/include/arch/x86/ia32/arch.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -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; diff --git a/include/arch/x86/ia32/gdbstub.h b/include/arch/x86/ia32/gdbstub.h new file mode 100644 index 00000000000..450928216f6 --- /dev/null +++ b/include/arch/x86/ia32/gdbstub.h @@ -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 +#include + +/** + * @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_ */ diff --git a/include/debug/gdbstub.h b/include/debug/gdbstub.h new file mode 100644 index 00000000000..efb14ed4381 --- /dev/null +++ b/include/debug/gdbstub.h @@ -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 diff --git a/include/sys/arch_interface.h b/include/sys/arch_interface.h index 7c323fa1ac2..92963928904 100644 --- a/include/sys/arch_interface.h +++ b/include/sys/arch_interface.h @@ -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 diff --git a/kernel/include/kernel_internal.h b/kernel/include/kernel_internal.h index 26e78fd689a..5d0419613f3 100644 --- a/kernel/include/kernel_internal.h +++ b/kernel/include/kernel_internal.h @@ -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 diff --git a/subsys/debug/CMakeLists.txt b/subsys/debug/CMakeLists.txt index 42862fb2764..6e198cce9bb 100644 --- a/subsys/debug/CMakeLists.txt +++ b/subsys/debug/CMakeLists.txt @@ -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 + ) diff --git a/subsys/debug/Kconfig b/subsys/debug/Kconfig index e96b3d8a1df..d618a262659 100644 --- a/subsys/debug/Kconfig +++ b/subsys/debug/Kconfig @@ -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 diff --git a/subsys/debug/gdbstub.c b/subsys/debug/gdbstub.c new file mode 100644 index 00000000000..2476af0a937 --- /dev/null +++ b/subsys/debug/gdbstub.c @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2020 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +LOG_MODULE_REGISTER(gdbstub); + +#include + +#include +#include +#include +#include +#include +#include + +#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 " + */ +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); diff --git a/subsys/debug/gdbstub/gdbstub_backend_serial.c b/subsys/debug/gdbstub/gdbstub_backend_serial.c new file mode 100644 index 00000000000..91b36b1e966 --- /dev/null +++ b/subsys/debug/gdbstub/gdbstub_backend_serial.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +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; +} diff --git a/subsys/debug/gdbstub_backend.h b/subsys/debug/gdbstub_backend.h new file mode 100644 index 00000000000..a9c5828a8ef --- /dev/null +++ b/subsys/debug/gdbstub_backend.h @@ -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 + +/** + * 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