diff --git a/drivers/serial/CMakeLists.txt b/drivers/serial/CMakeLists.txt index 1dffcd4f08..d5ca56d70d 100644 --- a/drivers/serial/CMakeLists.txt +++ b/drivers/serial/CMakeLists.txt @@ -22,6 +22,10 @@ set(SRCS serial.c serial_io.c) +if(CONFIG_SERIAL_GDBSTUB) + list(APPEND SRCS serial_gdbstub.c) +endif() + if(CONFIG_UART_PL011) list(APPEND SRCS serial_pl011.c) endif() diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 026e20d027..067795ea7e 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -41,6 +41,18 @@ config SERIAL_CONSOLE bool default n +config SERIAL_GDBSTUB + bool "GDBSTUB Serial support" + depends on ARCH_HAVE_DEBUG + default n + +config SERIAL_GDBSTUB_PATH + string "GDBSTUB Serial path" + depends on SERIAL_GDBSTUB + default "/dev/ttyS1" + ---help--- + The path to the serial device that will be used for GDB stub. + menuconfig UART_PL011 bool "PL011 Chip support" default n diff --git a/drivers/serial/Make.defs b/drivers/serial/Make.defs index e5cf014055..3975d45ccd 100644 --- a/drivers/serial/Make.defs +++ b/drivers/serial/Make.defs @@ -22,6 +22,10 @@ CSRCS += serial.c serial_io.c +ifeq ($(CONFIG_SERIAL_GDBSTUB),y) + CSRCS += serial_gdbstub.c +endif + ifeq ($(CONFIG_UART_PL011),y) CSRCS += serial_pl011.c endif diff --git a/drivers/serial/serial.c b/drivers/serial/serial.c index 7b8f059520..43a4552796 100644 --- a/drivers/serial/serial.c +++ b/drivers/serial/serial.c @@ -1836,6 +1836,13 @@ int uart_register(FAR const char *path, FAR uart_dev_t *dev) /* Register the serial driver */ +#ifdef CONFIG_SERIAL_GDBSTUB + if (strcmp(path, CONFIG_SERIAL_GDBSTUB_PATH) == 0) + { + return uart_gdbstub_register(dev); + } +#endif + sinfo("Registering %s\n", path); return register_driver(path, &g_serialops, 0666, dev); } diff --git a/drivers/serial/serial_gdbstub.c b/drivers/serial/serial_gdbstub.c new file mode 100644 index 0000000000..d603c77f43 --- /dev/null +++ b/drivers/serial/serial_gdbstub.c @@ -0,0 +1,202 @@ +/**************************************************************************** + * drivers/serial/serial_gdbstub.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +struct uart_gdbstub_s +{ + FAR struct uart_dev_s *dev; + FAR struct gdb_state_s *state; + FAR const struct uart_ops_s *org_ops; + struct uart_ops_s ops; + struct notifier_block nb; +}; + +static FAR struct uart_gdbstub_s *g_uart_gdbstub; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: uart_gdbstub_panic_callback + * + * Description: + * This is panic callback for gdbstub, If a crash occurs, + * you can debug it through gdb + * + ****************************************************************************/ + +static int uart_gdbstub_panic_callback(FAR struct notifier_block *nb, + unsigned long action, FAR void *data) +{ + FAR struct uart_gdbstub_s *uart_gdbstub = + container_of(nb, struct uart_gdbstub_s, nb); + + if (action != PANIC_KERNEL_FINAL) + { + return 0; + } + + _alert("Enter panic gdbstub mode, plase use gdb connect to debug\n"); + _alert("Please use gdb of the corresponding architecture to " + "connect to nuttx"); + _alert("such as: arm-none-eabi-gdb nuttx -ex \"set " + "target-charset ASCII\" -ex \"target remote /dev/ttyUSB0\"\n"); + + gdb_console_message(uart_gdbstub->state, "Enter panic gdbstub mode!\n"); + gdb_process(uart_gdbstub->state, GDBSTUB_STOPREASON_CTRLC, NULL); + return 0; +} + +/**************************************************************************** + * Name: uart_gdbstub_ctrlc + * + * Description: + * This is uart receive callback in interruption. + * The function is to accept the initial connection of Ctrl c and gdb. + * + ****************************************************************************/ + +static int uart_gdbstub_ctrlc(FAR struct uart_dev_s *dev, + FAR unsigned int *status) +{ + uart_disablerxint(dev); + gdb_process(g_uart_gdbstub->state, GDBSTUB_STOPREASON_CTRLC, NULL); + uart_enablerxint(dev); + return 0; +} + +/**************************************************************************** + * Name: uart_gdbstub_receive + * + * Description: + * This is gdbstub receive char function. + * + ****************************************************************************/ + +static ssize_t uart_gdbstub_receive(FAR void *priv, FAR void *buf, + size_t len) +{ + FAR struct uart_gdbstub_s *uart_gdbstub = priv; + FAR uart_dev_t *dev = uart_gdbstub->dev; + FAR char *ptr = buf; + unsigned int state; + size_t i = 0; + + while (i < len) + { + if (uart_gdbstub->org_ops->rxavailable(dev)) + { + ptr[i++] = g_uart_gdbstub->org_ops->receive(dev, &state); + } + } + + return len; +} + +/**************************************************************************** + * Name: uart_gdbstub_send + * + * Description: + * This is gdbstub send char function. + * + ****************************************************************************/ + +static ssize_t uart_gdbstub_send(FAR void *priv, FAR void *buf, size_t len) +{ + FAR struct uart_gdbstub_s *uart_gdbstub = priv; + FAR uart_dev_t *dev = uart_gdbstub->dev; + size_t i = 0; + + while (i < len) + { + if (uart_gdbstub->org_ops->txready(dev)) + { + uart_gdbstub->org_ops->send(dev, ((FAR char *)buf)[i++]); + } + } + + return len; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: uart_gdbstub_register + * + * Description: + * Use the uart device to register gdbstub. + * gdbstub run with serial interrupt. + * + ****************************************************************************/ + +int uart_gdbstub_register(FAR uart_dev_t *dev) +{ + FAR struct uart_gdbstub_s *uart_gdbstub; + + uart_gdbstub = kmm_malloc(sizeof(struct uart_gdbstub_s)); + if (uart_gdbstub == NULL) + { + return -ENOMEM; + } + + uart_gdbstub->state = gdb_state_init(uart_gdbstub_send, + uart_gdbstub_receive, + NULL, uart_gdbstub); + if (uart_gdbstub->state == NULL) + { + kmm_free(uart_gdbstub); + return -ENOMEM; + } + + g_uart_gdbstub = uart_gdbstub; + + memcpy(&uart_gdbstub->ops, dev->ops, sizeof(struct uart_ops_s)); + uart_gdbstub->dev = dev; + uart_gdbstub->org_ops = dev->ops; + uart_gdbstub->ops.receive = uart_gdbstub_ctrlc; + dev->ops = &uart_gdbstub->ops; + uart_setup(dev); + uart_attach(dev); + uart_enablerxint(dev); + + uart_gdbstub->nb.notifier_call = uart_gdbstub_panic_callback; + panic_notifier_chain_register(&uart_gdbstub->nb); + + return 0; +} diff --git a/include/nuttx/gdbstub.h b/include/nuttx/gdbstub.h index 0772e6f049..dfa9245d9c 100644 --- a/include/nuttx/gdbstub.h +++ b/include/nuttx/gdbstub.h @@ -27,6 +27,18 @@ #include +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define GDBSTUB_STOPREASON_NONE 0x00 +#define GDBSTUB_STOPREASON_WATCHPOINT_RO 0x01 +#define GDBSTUB_STOPREASON_WATCHPOINT_WO 0x02 +#define GDBSTUB_STOPREASON_WATCHPOINT_RW 0x03 +#define GDBSTUB_STOPREASON_BREAKPOINT 0x04 +#define GDBSTUB_STOPREASON_STEPPOINT 0x05 +#define GDBSTUB_STOPREASON_CTRLC 0x06 + /**************************************************************************** * Type Definitions ****************************************************************************/ @@ -108,6 +120,7 @@ int gdb_console_message(FAR struct gdb_state_s *state, FAR const char *msg); * ****************************************************************************/ -int gdb_process(FAR struct gdb_state_s *state); +int gdb_process(FAR struct gdb_state_s *state, int stopreason, + FAR void *stopaddr); #endif /* __INCLUDE_NUTTX_GDBSTUB_H */ diff --git a/include/nuttx/serial/serial.h b/include/nuttx/serial/serial.h index fb725f460d..aa7f7ff4de 100644 --- a/include/nuttx/serial/serial.h +++ b/include/nuttx/serial/serial.h @@ -522,6 +522,19 @@ void uart_reset_sem(FAR uart_dev_t *dev); int uart_check_special(FAR uart_dev_t *dev, const char *buf, size_t size); #endif +/**************************************************************************** + * Name: uart_gdbstub_register + * + * Description: + * Use the uart device to register gdbstub. + * gdbstub run with serial interrupt. + * + ****************************************************************************/ + +#ifdef CONFIG_SERIAL_GDBSTUB +int uart_gdbstub_register(FAR uart_dev_t *dev); +#endif + #undef EXTERN #if defined(__cplusplus) } diff --git a/libs/libc/gdbstub/lib_gdbstub.c b/libs/libc/gdbstub/lib_gdbstub.c index 1fe0e80771..433017f60c 100644 --- a/libs/libc/gdbstub/lib_gdbstub.c +++ b/libs/libc/gdbstub/lib_gdbstub.c @@ -30,7 +30,9 @@ #include #include +#include #include +#include #include /**************************************************************************** @@ -57,6 +59,8 @@ struct gdb_state_s gdb_monitor_func_t monitor; /* Monitor will be called when gdb * receive a monitor command */ + int last_stopreason; /* Last stop reason */ + FAR void *last_stopaddr; /* Last stop address */ pid_t pid; /* Gdb current thread */ FAR char *pkt_next; /* Pointer to next byte in packet */ char pkt_buf[1024]; /* Packet buffer */ @@ -107,8 +111,6 @@ static ssize_t gdb_bin2bin(FAR void *buf, size_t buf_len, /* Packet creation helpers */ static int gdb_send_ok_packet(FAR struct gdb_state_s *state); -static int gdb_send_signal_packet(FAR struct gdb_state_s *state, - unsigned char signal); static int gdb_send_error_packet(FAR struct gdb_state_s *state, unsigned char error); @@ -400,6 +402,12 @@ static int gdb_recv_packet(FAR struct gdb_state_s *state) break; } + else if (ret == ASCII_ETX) + { + state->pkt_buf[0] = ASCII_ETX; + state->pkt_len = 1; + return 0; + } } /* Read until checksum */ @@ -432,6 +440,12 @@ static int gdb_recv_packet(FAR struct gdb_state_s *state) } while (state->pkt_len == 0); /* Ignore empty packets */ + ret = state->recv(state->priv, buf, 2); /* Receive the checksum */ + if (ret < 0) + { + return ret; + } + #ifdef CONFIG_LIB_GDBSTUB_DEBUG { size_t p; @@ -452,12 +466,6 @@ static int gdb_recv_packet(FAR struct gdb_state_s *state) } #endif - ret = state->recv(state->priv, buf, 2); /* Receive the checksum */ - if (ret < 0) - { - return ret; - } - ret = gdb_hex2bin(&csum, 1, buf, 2); if (ret < 0) { @@ -773,39 +781,6 @@ static int gdb_send_ok_packet(FAR struct gdb_state_s *state) return gdb_send_packet(state); } -/**************************************************************************** - * Name: gdb_send_signal_packet - * - * Description: - * Send a signal packet (S AA). - * - * Input Parameters: - * state - The pointer to the GDB state structure. - * signal - The signal to send. - * - * Returned Value: - * Zero on success. - * Negative value on error. - * - ****************************************************************************/ - -static int gdb_send_signal_packet(FAR struct gdb_state_s *state, - unsigned char signal) -{ - int ret; - - state->pkt_buf[0] = 'S'; - ret = gdb_bin2hex(&state->pkt_buf[1], sizeof(state->pkt_buf) - 1, - &signal, 1); - if (ret < 0) - { - return ret; - } - - state->pkt_len = 1 + ret; - return gdb_send_packet(state); -} - /**************************************************************************** * Name: gdb_send_error_packet * @@ -1176,6 +1151,7 @@ static int gdb_query(FAR struct gdb_state_s *state) static const char thread_extra_info[] = "ThreadExtraInfo"; static const char fthread_info[] = "fThreadInfo"; static const char sthread_info[] = "sThreadInfo"; + static const char supported_info[] = "Supported"; static const char r_cmd[] = "Rcmd"; if (memcmp(&state->pkt_buf[1], fthread_info, @@ -1195,6 +1171,20 @@ static int gdb_query(FAR struct gdb_state_s *state) state->pkt_len = 1; gdb_send_packet(state); } + else if (memcmp(&state->pkt_buf[1], supported_info, + sizeof(supported_info) - 1) == 0) + { +#ifdef CONFIG_ARCH_HAVE_DEBUG + state->pkt_len = sprintf(state->pkt_buf, + "hwbreak+;PacketSize=%x", + sizeof(state->pkt_buf)); +#else + state->pkt_len = sprintf(state->pkt_buf, + "PacketSize=%x", + sizeof(state->pkt_buf)); +#endif + gdb_send_packet(state); + } else if (memcmp(&state->pkt_buf[1], thread_extra_info, sizeof(thread_extra_info) - 1) == 0) { @@ -1325,7 +1315,7 @@ static int gdb_is_thread_active(FAR struct gdb_state_s *state) * 0 if successful. * Negative value on error. * - * Note : Comand Format: Hc + * Note : Comand Format: Hg * Rsponse Format: OK ****************************************************************************/ @@ -1335,6 +1325,11 @@ static int gdb_thread_context(FAR struct gdb_state_s *state) uintptr_t pid; int ret; + if (state->pkt_buf[1] != 'g') + { + return -EINVAL; + } + state->pkt_next += 2; ret = gdb_expect_integer(state, &pid); if (ret < 0) @@ -1370,13 +1365,260 @@ static int gdb_thread_context(FAR struct gdb_state_s *state) * 0 if successful. * Negative value on error. * + * Note : Rsponse Format: T AA n1:r1;n2:r2;... + * The program received signal number AA + * n is thread id in current. + * r is stop reason. + * ****************************************************************************/ -static int gdb_send_stop(FAR struct gdb_state_s *state) +static int gdb_send_stop(FAR struct gdb_state_s *state, int stopreason, + FAR void *stopaddr) { - return gdb_send_signal_packet(state, 0x00); + int ret; + + state->pid = _SCHED_GETTID(); +retry: + switch (stopreason) + { + case GDBSTUB_STOPREASON_WATCHPOINT_RO: + ret = sprintf(state->pkt_buf, "T05thread:%x;rwatch:%" PRIxPTR ";", + state->pid + 1, (uintptr_t)stopaddr); + break; + case GDBSTUB_STOPREASON_WATCHPOINT_WO: + ret = sprintf(state->pkt_buf, "T05thread:%x;awatch:%" PRIxPTR ";", + state->pid + 1, (uintptr_t)stopaddr); + break; + case GDBSTUB_STOPREASON_WATCHPOINT_RW: + ret = sprintf(state->pkt_buf, "T05thread:%x;watch:%" PRIxPTR ";", + state->pid + 1, (uintptr_t)stopaddr); + break; + case GDBSTUB_STOPREASON_BREAKPOINT: + ret = sprintf(state->pkt_buf, "T05thread:%x;hwbreak:;", + state->pid + 1); + break; + case GDBSTUB_STOPREASON_STEPPOINT: + if (state->last_stopreason == GDBSTUB_STOPREASON_WATCHPOINT_RW || + state->last_stopreason == GDBSTUB_STOPREASON_WATCHPOINT_WO) + { + stopreason = state->last_stopreason; + stopaddr = state->last_stopaddr; + goto retry; + } + + case GDBSTUB_STOPREASON_CTRLC: + default: + ret = sprintf(state->pkt_buf, "T05thread:%d;", state->pid + 1); + } + + if (ret < 0) + { + return ret; + } + + state->pkt_len = ret; + return gdb_send_packet(state); } +#ifdef CONFIG_ARCH_HAVE_DEBUG + +/**************************************************************************** + * Name: gdbstub_debugpoint_callback + * + * Description: + * The debugpoint callback is used by GDB to request. + * + ****************************************************************************/ + +static void gdbstub_debugpoint_callback(int type, FAR void *addr, + size_t size, FAR void *arg) +{ + int stopreason; + + switch (type) + { + case DEBUGPOINT_BREAKPOINT: + stopreason = GDBSTUB_STOPREASON_BREAKPOINT; + break; + case DEBUGPOINT_WATCHPOINT_RO: + stopreason = GDBSTUB_STOPREASON_WATCHPOINT_RO; + break; + case DEBUGPOINT_WATCHPOINT_WO: + stopreason = GDBSTUB_STOPREASON_WATCHPOINT_WO; + break; + case DEBUGPOINT_WATCHPOINT_RW: + stopreason = GDBSTUB_STOPREASON_WATCHPOINT_RW; + break; + case DEBUGPOINT_STEPPOINT: + stopreason = GDBSTUB_STOPREASON_STEPPOINT; + up_debugpoint_remove(DEBUGPOINT_STEPPOINT, NULL, 0); + break; + default: + return; + } + + gdb_process(arg, stopreason, addr); +} + +/**************************************************************************** + * Name: gdb_debugpoint + * + * Description: + * The debugpoint packet is used by GDB to request information from + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * enable - Enable or disable debugpoint. + * + * Returned Value: + * 0 if successful. + * Negative value on error. + * + * Note : Comand Format: Z/z type,addr,length + * Rsponse Format: OK + * Z is set breakpoint. + * z is clear breakpoint. + * type: 0 is software breakpoint. + * 1 is hardware breakpoint. + * 2 is write watchpoint. + * 3 is read watchpoint. + * 4 is read/write watchpoint. + * length: is the length of watchpoint. + * if is breakpoint, length is instruction type. + * + ****************************************************************************/ + +static int gdb_debugpoint(FAR struct gdb_state_s *state, bool enable) +{ + uintptr_t type; + uintptr_t addr; + uintptr_t size; + int ret; + + state->pkt_next += 1; + ret = gdb_expect_integer(state, &type); + if (ret < 0) + { + return ret; + } + + ret = gdb_expect_seperator(state, ','); + if (ret < 0) + { + return ret; + } + + ret = gdb_expect_integer(state, &addr); + if (ret < 0) + { + return ret; + } + + ret = gdb_expect_seperator(state, ','); + if (ret < 0) + { + return ret; + } + + ret = gdb_expect_integer(state, &size); + if (ret < 0) + { + return ret; + } + + switch (type) + { + case 0: /* just use hardware break */ + case 1: + type = DEBUGPOINT_BREAKPOINT; + break; + case 2: + type = DEBUGPOINT_WATCHPOINT_WO; + break; + case 3: + type = DEBUGPOINT_WATCHPOINT_RO; + break; + case 4: + type = DEBUGPOINT_WATCHPOINT_RW; + break; + default: + return -EPROTONOSUPPORT; + } + + if (enable) + { + ret = up_debugpoint_add(type, (FAR void *)addr, size, + gdbstub_debugpoint_callback, state); + } + else + { + ret = up_debugpoint_remove(type, (FAR void *)addr, size); + } + + if (ret < 0) + { + return ret; + } + + return gdb_send_ok_packet(state); +} + +/**************************************************************************** + * Name: gdb_step + * + * Description: + * The handle step packet is used by GDB to request information from + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * 0 if successful. + * Negative value on error. + * + * Note : Comand Format: s + * Rsponse Format: OK + * + ****************************************************************************/ + +static int gdb_step(FAR struct gdb_state_s *state) +{ + int ret = up_debugpoint_add(DEBUGPOINT_STEPPOINT, NULL, 0, + gdbstub_debugpoint_callback, state); + + if (ret < 0) + { + return ret; + } + + return gdb_send_ok_packet(state); +} + +/**************************************************************************** + * Name: gdb_continue + * + * Description: + * The handle continue packet is used by GDB to request information from + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * 0 if successful. + * Negative value on error. + * + * Note : Comand Format: c + * Rsponse Format: OK + * + ****************************************************************************/ + +static int gdb_continue(FAR struct gdb_state_s *state) +{ + return gdb_send_ok_packet(state); +} + +#endif + /**************************************************************************** * Public Data ****************************************************************************/ @@ -1497,10 +1739,16 @@ int gdb_console_message(FAR struct gdb_state_s *state, FAR const char *msg) * ****************************************************************************/ -int gdb_process(FAR struct gdb_state_s *state) +int gdb_process(FAR struct gdb_state_s *state, int stopreason, + FAR void *stopaddr) { int ret; + if (stopreason != GDBSTUB_STOPREASON_NONE) + { + gdb_send_stop(state, stopreason, stopaddr); + } + while ((ret = gdb_recv_packet(state)) >= 0) { /* Handle one letter commands */ @@ -1508,7 +1756,7 @@ int gdb_process(FAR struct gdb_state_s *state) switch (state->pkt_buf[0]) { case '?': /* gdbserial status */ - ret = gdb_send_stop(state); + ret = gdb_send_stop(state, stopreason, stopaddr); break; case 'g': /* Read registers */ ret = gdb_read_registers(state); @@ -1540,6 +1788,33 @@ int gdb_process(FAR struct gdb_state_s *state) case 'k': /* Kill request */ ret = -ECONNRESET; break; + case ASCII_ETX: /* Ctrl C */ + ret = gdb_send_ok_packet(state); + break; +#ifdef CONFIG_ARCH_HAVE_DEBUG + case 'Z': /* Insert breakpoint/watchpoint */ + ret = gdb_debugpoint(state, true); + break; + case 'z': /* Remove breakpoint/watchpoint */ + ret = gdb_debugpoint(state, false); + break; + case 's': /* Single step */ + ret = gdb_step(state); + if (ret < 0) + { + break; + } + + goto out; + case 'c': /* Continue */ + ret = gdb_continue(state); + if (ret < 0) + { + break; + } + + goto out; +#endif default: ret = -EPROTONOSUPPORT; } @@ -1559,6 +1834,11 @@ int gdb_process(FAR struct gdb_state_s *state) } } +#ifdef CONFIG_ARCH_HAVE_DEBUG +out: +#endif + state->last_stopreason = stopreason; + state->last_stopaddr = stopaddr; return ret; }