diff --git a/include/nuttx/gdbstub.h b/include/nuttx/gdbstub.h new file mode 100644 index 0000000000..0772e6f049 --- /dev/null +++ b/include/nuttx/gdbstub.h @@ -0,0 +1,113 @@ +/**************************************************************************** + * include/nuttx/gdbstub.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_GDBSTUB_H +#define __INCLUDE_NUTTX_GDBSTUB_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Type Definitions + ****************************************************************************/ + +struct gdb_state_s; +typedef CODE ssize_t (*gdb_send_func_t)(FAR void *priv, FAR void *buf, + size_t len); +typedef CODE ssize_t (*gdb_recv_func_t)(FAR void *priv, FAR void *buf, + size_t len); + +typedef CODE int (*gdb_monitor_func_t)(FAR struct gdb_state_s *state, + FAR const char *cmd); + +/**************************************************************************** + * Name: gdb_state_init + * + * Description: + * Initialize the GDB state structure. + * + * Input Parameters: + * send - The pointer to the send function. + * recv - The pointer to the receive function. + * monitor - The pointer to the monitor function. + * priv - The pointer to the private data. + * + * Returned Value: + * The pointer to the GDB state structure on success. + * NULL on failure. + * + ****************************************************************************/ + +FAR struct gdb_state_s *gdb_state_init(gdb_send_func_t send, + gdb_recv_func_t recv, + gdb_monitor_func_t monitor, + FAR void *priv); + +/**************************************************************************** + * Name: gdb_state_uninit + * + * Description: + * Uninitialize the GDB state structure. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + ****************************************************************************/ + +void gdb_state_uninit(FAR struct gdb_state_s *state); + +/**************************************************************************** + * Name: gdb_console_message + * + * Description: + * Send a message to the GDB console (via O XX... packet). + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * Zero on success. + * Negative value on error. + * + ****************************************************************************/ + +int gdb_console_message(FAR struct gdb_state_s *state, FAR const char *msg); + +/**************************************************************************** + * Name: gdb_process + * + * Description: + * Main debug loop. Handles commands. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * Zero if successful. + * Negative value on error. + * + ****************************************************************************/ + +int gdb_process(FAR struct gdb_state_s *state); + +#endif /* __INCLUDE_NUTTX_GDBSTUB_H */ diff --git a/libs/libc/Kconfig b/libs/libc/Kconfig index 1e26b3ee38..ac59bac040 100644 --- a/libs/libc/Kconfig +++ b/libs/libc/Kconfig @@ -14,6 +14,7 @@ source "libs/libc/string/Kconfig" source "libs/libc/pthread/Kconfig" source "libs/libc/dlfcn/Kconfig" source "libs/libc/modlib/Kconfig" +source "libs/libc/gdbstub/Kconfig" source "libs/libc/grp/Kconfig" source "libs/libc/pwd/Kconfig" source "libs/libc/locale/Kconfig" diff --git a/libs/libc/Makefile b/libs/libc/Makefile index 40e87f58fb..b08dabe046 100644 --- a/libs/libc/Makefile +++ b/libs/libc/Makefile @@ -30,6 +30,7 @@ include dlfcn/Make.defs include errno/Make.defs include eventfd/Make.defs include fixedmath/Make.defs +include gdbstub/Make.defs include grp/Make.defs include hex2bin/Make.defs include inttypes/Make.defs diff --git a/libs/libc/gdbstub/Kconfig b/libs/libc/gdbstub/Kconfig new file mode 100644 index 0000000000..faa91cdb90 --- /dev/null +++ b/libs/libc/gdbstub/Kconfig @@ -0,0 +1,20 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config LIB_GDBSTUB + tristate "GDBSTUB" + depends on DEBUG_TCBINFO + ---help--- + Enable support for gdbstub. + +if LIB_GDBSTUB + +config LIB_GDBSTUB_DEBUG + bool "Gdbstub Debug Info" + default n + ---help--- + Add debug info to gdbstub + +endif diff --git a/libs/libc/gdbstub/Make.defs b/libs/libc/gdbstub/Make.defs new file mode 100644 index 0000000000..77c2180c80 --- /dev/null +++ b/libs/libc/gdbstub/Make.defs @@ -0,0 +1,32 @@ +############################################################################ +# libs/libc/gdbstub/Make.defs +# +# 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. +# +############################################################################ + +ifeq ($(CONFIG_LIB_GDBSTUB),y) + +# Add the internal C files to the build + +CSRCS += lib_gdbstub.c + +# Add the userfs directory to the build + +DEPPATH += --dep-path gdbstub +VPATH += :gdbstub + +endif diff --git a/libs/libc/gdbstub/lib_gdbstub.c b/libs/libc/gdbstub/lib_gdbstub.c new file mode 100644 index 0000000000..67b4dbb5ad --- /dev/null +++ b/libs/libc/gdbstub/lib_gdbstub.c @@ -0,0 +1,1559 @@ +/**************************************************************************** + * libs/libc/gdbstub/lib_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 + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_LIB_GDBSTUB_DEBUG +# define GDB_DEBUG(...) syslog(LOG_DEBUG, ##__VA_ARGS__) +# define GDB_ASSERT() __assert(__FILE__, __LINE__, 0) +#else +# define GDB_DEBUG(...) +# define GDB_ASSERT() do {} while (0) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct gdb_state_s +{ + gdb_send_func_t send; /* Send buffer to gdb */ + gdb_recv_func_t recv; /* Recv buffer from gdb */ + FAR void *priv; /* Private data for transport */ + gdb_monitor_func_t monitor; /* Monitor will be called when gdb + * receive a monitor command + */ + pid_t pid; /* Gdb current thread */ + FAR char *pkt_next; /* Pointer to next byte in packet */ + char pkt_buf[1024]; /* Packet buffer */ + size_t pkt_len; /* Packet send and receive length */ + uint8_t running_regs[XCPTCONTEXT_SIZE]; /* Registers of running thread */ + size_t size; /* Size of registers */ + uintptr_t registers[0]; /* Registers of other threads */ +}; + +typedef CODE ssize_t (*gdb_format_func_t)(FAR void *buf, size_t buf_len, + FAR const void *data, + size_t data_len); + +/* Calculate remaining space in packet from ptr_next position. */ + +#define gdb_remaining_len(state) ((state)->pkt_len - \ + ((state)->pkt_next - (state)->pkt_buf)) + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions Prototypes + ****************************************************************************/ + +/* System functions, supported by all stubs */ + +static int gdb_getchar(FAR struct gdb_state_s *state); +static int gdb_putchar(FAR struct gdb_state_s *state, int ch); + +/* Packet functions */ + +static int gdb_send_packet(FAR struct gdb_state_s *state); +static int gdb_recv_packet(FAR struct gdb_state_s *state); +static int gdb_checksum(FAR const char *buf, size_t len); +static int gdb_recv_ack(FAR struct gdb_state_s *state); + +/* Data format */ + +static ssize_t gdb_bin2hex(FAR void *buf, size_t buf_len, + FAR const void *data, size_t data_len); +static ssize_t gdb_hex2bin(FAR void *buf, size_t buf_len, + FAR const void *data, size_t data_len); +static ssize_t gdb_bin2bin(FAR void *buf, size_t buf_len, + FAR const void *data, size_t data_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); + +/* Command functions */ + +static ssize_t gdb_get_memory(FAR struct gdb_state_s *state, + FAR void *buf, size_t buf_len, + uintptr_t addr, size_t len, + gdb_format_func_t format); +static ssize_t gdb_put_memory(FAR struct gdb_state_s *state, + FAR const void *buf, size_t buf_len, + uintptr_t addr, size_t len, + gdb_format_func_t format); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Packet buffer Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: gdb_expect_seperator + * + * Description: + * Check if the next byte in the packet is a seperator. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * c - The expected seperator. + * + * Returned Value: + * 0 on success, -EINVAL on error. + * + ****************************************************************************/ + +static int gdb_expect_seperator(FAR struct gdb_state_s *state, char c) +{ + if (!state->pkt_next || *state->pkt_next != c) + { + return -EINVAL; + } + else + { + state->pkt_next++; + } + + return 0; +} + +/**************************************************************************** + * Name: gdb_expect_integer + * + * Description: + * Parse an integer argument from the packet. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * arg - The parsed integer argument. + * + * Returned Value: + * 0 on success + * + ****************************************************************************/ + +static int gdb_expect_integer(FAR struct gdb_state_s *state, + FAR uintptr_t *arg) +{ + size_t len = gdb_remaining_len(state); + char tmp = state->pkt_next[len]; + + state->pkt_next[len] = '\0'; + *arg = strtoul(state->pkt_next, &state->pkt_next, 16); + state->pkt_next[len] = tmp; + return 0; +} + +/**************************************************************************** + * Name: gdb_expect_addr_lenth + * + * Description: + * Parse an address and length argument from the packet. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * addr - The parsed address. + * length - The parsed length. + * + * Returned Value: + * 0 on success, -EINVAL on error. + * + ****************************************************************************/ + +static int gdb_expect_addr_lenth(FAR struct gdb_state_s *state, + FAR uintptr_t *addr, FAR size_t *length) +{ + int ret; + + ret = gdb_expect_integer(state, addr); + if (ret < 0) + { + return ret; + } + + ret = gdb_expect_seperator(state, ','); + if (ret < 0) + { + return ret; + } + + return gdb_expect_integer(state, length); +} + +/**************************************************************************** + * Packet Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: gdb_putchar + * + * Description: + * Put a character to the GDB server. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * ch - The character to be sent. + * + * Returned Value: + * The character sent to the GDB server. + * + ****************************************************************************/ + +static int gdb_putchar(FAR struct gdb_state_s *state, int ch) +{ + unsigned char tmp = ch & 0xff; + ssize_t ret; + + ret = state->send(state->priv, &tmp, 1); + if (ret < 0) + { + return ret; + } + + return tmp; +} + +/**************************************************************************** + * Name: gdb_getchar + * + * Description: + * Get a character from the GDB server. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * The character read from the GDB server. + * + ****************************************************************************/ + +static int gdb_getchar(FAR struct gdb_state_s *state) +{ + unsigned char tmp; + ssize_t ret; + + ret = state->recv(state->priv, &tmp, 1); + if (ret < 0) + { + return ret; + } + + return tmp; +} + +/**************************************************************************** + * Name: gdb_send_packet + * + * Description: + * Transmits a packet of data. + * Packets are of the form: $# + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * 0 if the packet was transmitted and acknowledged. + * 1 if the packet was transmitted but not acknowledged. + * Negative value on error. + * + ****************************************************************************/ + +static int gdb_send_packet(FAR struct gdb_state_s *state) +{ + unsigned char buf[3]; + unsigned char csum; + int ret; + + ret = gdb_putchar(state, '$'); /* Send packet start */ + if (ret < 0) + { + return ret; + } + +#ifdef CONFIG_LIB_GDBSTUB_DEBUG + { + size_t p; + GDB_DEBUG("-> "); + for (p = 0; p < state->pkt_len; p++) + { + if (isprint(state->pkt_buf[p])) + { + GDB_DEBUG("%c", state->pkt_buf[p]); + } + else + { + GDB_DEBUG("\\x%02x", state->pkt_buf[p] & 0xff); + } + } + + GDB_DEBUG("\n"); + } +#endif + + /* Send packet data */ + + ret = state->send(state->priv, state->pkt_buf, state->pkt_len); + if (ret < 0) + { + return ret; + } + + /* Send the checksum */ + + buf[0] = '#'; + csum = gdb_checksum(state->pkt_buf, state->pkt_len); + ret = gdb_bin2hex(buf + 1, sizeof(buf) - 1, &csum, 1); + if (ret < 0) + { + return ret; + } + + ret = state->send(state->priv, buf, sizeof(buf)); + if (ret < 0) + { + return ret; + } + + return gdb_recv_ack(state); +} + +/**************************************************************************** + * Name: gdb_recv_packet + * + * Description: + * Receives a packet of data, assuming a 7-bit clean connection. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * 0 if the packet was received. + * -EIO if the packet was not received. + * -EOVERFLOW if the packet was too long. + * Negative value on error. + * + ****************************************************************************/ + +static int gdb_recv_packet(FAR struct gdb_state_s *state) +{ + unsigned char buf[2]; + unsigned char csum; + int ret; + + /* Wait for packet start */ + + do + { + while (1) + { + ret = gdb_getchar(state); + if (ret < 0) + { + return ret; + } + else if (ret == '$') + { + /* Detected start of packet. */ + + break; + } + } + + /* Read until checksum */ + + state->pkt_len = 0; + while (1) + { + ret = gdb_getchar(state); + if (ret < 0) /* Error receiving character */ + { + return ret; + } + else if (ret == '#') /* End of packet */ + { + break; + } + else /* Check for space */ + { + if (state->pkt_len >= sizeof(state->pkt_buf)) + { + GDB_DEBUG("packet buffer overflow\n"); + return -EOVERFLOW; + } + + /* Store character and update checksum */ + + state->pkt_buf[state->pkt_len++] = (char)ret; + } + } + } + while (state->pkt_len == 0); /* Ignore empty packets */ + +#ifdef CONFIG_LIB_GDBSTUB_DEBUG + { + size_t p; + GDB_DEBUG("<- "); + for (p = 0; p < state->pkt_len; p++) + { + if (isprint(state->pkt_buf[p])) + { + GDB_DEBUG("%c", state->pkt_buf[p]); + } + else + { + GDB_DEBUG("\\x%02x", state->pkt_buf[p] & 0xff); + } + } + + GDB_DEBUG("\n"); + } +#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) + { + return ret; + } + + if (csum != gdb_checksum(state->pkt_buf, state->pkt_len)) /* Verify */ + { + GDB_DEBUG("received packet with bad checksum\n"); + gdb_putchar(state, '-'); /* Send packet nack */ + return -EIO; + } + + gdb_putchar(state, '+'); /* Send packet ack */ + state->pkt_next = state->pkt_buf; + return 0; +} + +/**************************************************************************** + * Name: gdb_checksum + * + * Description: + * Calculate 8-bit checksum of a buffer. + * + * Input Parameters: + * buf - The buffer to checksum. + * + * Returned Value: + * 8-bit checksum + * + ****************************************************************************/ + +static int gdb_checksum(FAR const char *buf, size_t len) +{ + unsigned char csum = 0; + + while (len--) + { + csum += *buf++; + } + + return csum; +} + +/**************************************************************************** + * Name: gdb_recv_ack + * + * Description: + * Receive a packet acknowledgment. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * 0 if an ACK (+) was received + * 1 if a NACK (-) was received + * -EINVAL if a bad response was received + * + ****************************************************************************/ + +static int gdb_recv_ack(FAR struct gdb_state_s *state) +{ + int response; + + switch (response = gdb_getchar(state)) /* Wait for packet ack */ + { + case '+': /* Packet acknowledged */ + return 0; + case '-': /* Packet negative acknowledged */ + return 1; + default: /* Bad response! */ + GDB_DEBUG("received bad packet response: 0x%2x\n", response); + return -EINVAL; + } +} + +/**************************************************************************** + * Data Encoding/Decoding + ****************************************************************************/ + +/**************************************************************************** + * Name: gdb_bin2hex + * + * Description: + * Encode data to its hex-value representation in a buffer. + * + * Input Parameters: + * buf - The buffer to encode to. + * buf_len - The length of the buffer. + * data - The data to encode. + * data_len - The length of the data to encode. + * + * Returned Value: + * The number of bytes written to buf on success. + * -EOVERFLOW if the buffer is too small. + * + ****************************************************************************/ + +static ssize_t gdb_bin2hex(FAR void *buf, size_t buf_len, + FAR const void *data, size_t data_len) +{ + FAR const char *in = data; + FAR char *out = buf; + size_t pos; + + if (buf_len < data_len * 2) + { + return -EOVERFLOW; /* Buffer too small */ + } + + for (pos = 0; pos < data_len; pos++) + { + itoa(in[pos] >> 4 & 0xf, out++, 16); + itoa(in[pos] & 0xf, out++, 16); + } + + return data_len * 2; +} + +/**************************************************************************** + * Name: gdb_hex2bin + * + * Description: + * Decode data from its hex-value representation to a buffer. + * + * Input Parameters: + * buf - The buffer containing the encoded data. + * buf_len - The length of the buffer. + * data - The buffer to store the decoded data. + * data_len - The length of the data to decode. + * + * Returned Value: + * The number of bytes written to data on success. + * -EOVERFLOW if the buffer is too small. + * Negative value on error. + * + ****************************************************************************/ + +static int gdb_hex2bin(FAR void *buf, size_t buf_len, + FAR const void *data, size_t data_len) +{ + FAR const char *in = data; + FAR char *out = buf; + size_t pos; + + if (buf_len * 2 < data_len) + { + return -EOVERFLOW; /* Buffer too small */ + } + + for (pos = 0; pos < data_len; pos += 2) + { + char ch[3] = + { + in[pos], in[pos + 1], 0 + }; + + out[pos / 2] = strtoul(ch, NULL, 16); /* Decode high nibble */ + if (out[pos / 2] == 0 && errno) + { + GDB_ASSERT(); + return -errno; /* Buffer contained junk. */ + } + } + + return data_len / 2; +} + +/**************************************************************************** + * Name: gdb_bin2bin + * + * Description: + * Decode data from its bin-value representation to a buffer. + * + * Input Parameters: + * buf - The buffer containing the encoded data. + * buf_len - The length of the buffer. + * data - The buffer to store the decoded data. + * data_len - The length of the data to decode. + * + * Returned Value: + * The number of bytes written to data on success. + * -EOVERFLOW if the output buffer is too small. + * -EINVAL if the input buffer is invalid. + * + ****************************************************************************/ + +static ssize_t gdb_bin2bin(FAR void *buf, size_t buf_len, + FAR const void *data, size_t data_len) +{ + FAR const char *in = data; + FAR char *out = buf; + size_t out_pos = 0; + size_t in_pos; + + for (in_pos = 0; in_pos < data_len; in_pos++) + { + if (out_pos >= buf_len) + { + GDB_ASSERT(); + return -EOVERFLOW; /* Output buffer overflow */ + } + + if (in[in_pos] == '}') /* The next byte is escaped! */ + { + if (in_pos + 1 >= data_len) + { + /* There's an escape character, but no escaped character + * following the escape character. + */ + + GDB_ASSERT(); + return -EINVAL; + } + + in_pos++; + out[out_pos++] = in[in_pos] ^ 0x20; + } + else + { + out[out_pos++] = in[in_pos]; + } + } + + return out_pos; +} + +/**************************************************************************** + * Command Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: gdb_get_memory + * + * Description: + * Get data from memory. + * + * Input Parameters: + * state - The state structure. + * buf - The buffer to store the encoded data. + * buf_len - The length of the buffer. + * addr - The address to read from. + * len - The length of the data to read. + * format - The formatting function to use. + * + * Returned Value: + * The number of bytes written to buf on success. + * Negative value on error. + * + ****************************************************************************/ + +static ssize_t gdb_get_memory(FAR struct gdb_state_s *state, + FAR void *buf, size_t buf_len, + uintptr_t addr, size_t len, + gdb_format_func_t format) +{ + return format(buf, buf_len, (FAR const void *)addr, len); +} + +/**************************************************************************** + * Name: gdb_put_memory + * + * Description: + * Put data from buf into memory. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * buf - The buffer containing the encoded data. + * buf_len - The length of the buffer. + * addr - The address to write to. + * len - The length of the data to write. + * format - The formatting function to use. + * + * Returned Value: + * The number of bytes written to addr on success. + * Negative value on error. + * + ****************************************************************************/ + +static ssize_t gdb_put_memory(FAR struct gdb_state_s *state, + FAR const void *buf, size_t buf_len, + uintptr_t addr, size_t len, + gdb_format_func_t format) +{ + return format((FAR void *)addr, len, buf, buf_len); +} + +/**************************************************************************** + * Packet Creation Helpers + ****************************************************************************/ + +/**************************************************************************** + * Name: gdb_send_ok_packet + * + * Description: + * Send OK packet. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * Zero on success. + * Negative value on error. + * + ****************************************************************************/ + +static int gdb_send_ok_packet(FAR struct gdb_state_s *state) +{ + state->pkt_buf[0] = 'O'; + state->pkt_buf[1] = 'K'; + state->pkt_len = 2; + + 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 + * + * Description: + * Send a error packet (E AA). + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * error - The error to send. + * + * Returned Value: + * Zero on success. + * Negative value on error. + * + ****************************************************************************/ + +static int gdb_send_error_packet(FAR struct gdb_state_s *state, + unsigned char error) +{ + int ret; + + state->pkt_buf[0] = 'E'; + ret = gdb_bin2hex(&state->pkt_buf[1], sizeof(state->pkt_buf) - 1, + &error, 1); + if (ret < 0) + { + return ret; + } + + state->pkt_len = 1 + ret; + return gdb_send_packet(state); +} + +/**************************************************************************** + * Communication Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: gdb_get_registers + * + * Description: + * Get the registers of the specified task. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + ****************************************************************************/ + +static void gdb_get_registers(FAR struct gdb_state_s *state) +{ + FAR struct tcb_s *tcb = nxsched_get_tcb(state->pid); + FAR uint8_t *reg; + int i; + + reg = (FAR uint8_t *)tcb->xcp.regs; + if (state->pid == getpid()) + { + if (up_interrupt_context()) + { + reg = (FAR uint8_t *)CURRENT_REGS; + } + else + { + up_saveusercontext(state->running_regs); + reg = state->running_regs; + } + } + + for (i = 0; i < state->size / sizeof(uintptr_t); i++) + { + if (g_tcbinfo.reg_off.p[i] == UINT16_MAX) + { + state->registers[i] = 0; + } + else + { + state->registers[i] = + *(FAR uintptr_t *)(reg + g_tcbinfo.reg_off.p[i]); + } + } +} + +/**************************************************************************** + * Name: gdb_read_registers + * + * Description: + * Read Registers Command Format: g. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * Zero on success. + * Negative value on error. + * + * Note: Comand Format: g. + * Response Format: xxxxxxxxyyyyyyyyy... + ****************************************************************************/ + +static int gdb_read_registers(FAR struct gdb_state_s *state) +{ + int ret; + + gdb_get_registers(state); + ret = gdb_bin2hex(state->pkt_buf, sizeof(state->pkt_buf), + state->registers, state->size); + if (ret < 0) + { + return ret; + } + + state->pkt_len = ret; + gdb_send_packet(state); + + return 0; +} + +/**************************************************************************** + * Name: gdb_write_registers + * + * Description: + * Write Registers Command Format: G XX... + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * Zero on success. + * Negative value on error. + * + * Note: This function is not really change the register values. + * Comand Format: Gxxxxxxxxyyyyyyyyy + * Response Format: OK + ****************************************************************************/ + +static int gdb_write_registers(FAR struct gdb_state_s *state) +{ + int ret; + + ret = gdb_hex2bin(state->registers, sizeof(state->registers), + state->pkt_buf + 1, state->pkt_len - 1); + if (ret < 0) + { + return ret; + } + + gdb_send_ok_packet(state); + return 0; +} + +/**************************************************************************** + * Name: gdb_write_register + * + * Description: + * Write a Register Command Format: P n. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * Zero on success. + * Negative value on error. + * + * Note: Comand Format: Pn. + * Response Format: OK + ****************************************************************************/ + +static int gdb_read_register(FAR struct gdb_state_s *state) +{ + uintptr_t addr; + int ret; + + state->pkt_next++; + ret = gdb_expect_integer(state, &addr); + if (ret < 0) + { + return ret; + } + + gdb_get_registers(state); + if (addr >= state->size) + { + return 0; + } + + ret = gdb_bin2hex(state->pkt_buf, sizeof(state->pkt_buf), + &state->registers[addr], sizeof(state->registers[addr])); + if (ret < 0) + { + return ret; + } + + state->pkt_len = ret; + gdb_send_packet(state); + return 0; +} + +/**************************************************************************** + * Name: gdb_read_memory + * + * Description: + * Read Memory Command Format: m addr,length + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * The number of bytes read if successful. + * Negative value on error. + * + * Note: Comand Format: mAAAAAAAAA,LLLLLLLL + * Response Format: XXXXXXXXYYYYYYYYY... + ****************************************************************************/ + +static int gdb_read_memory(FAR struct gdb_state_s *state) +{ + uintptr_t addr; + size_t length; + int ret; + + state->pkt_next++; + ret = gdb_expect_addr_lenth(state, &addr, &length); + if (ret < 0) + { + return ret; + } + + ret = gdb_get_memory(state, state->pkt_buf, sizeof(state->pkt_buf), + addr, length, gdb_bin2hex); + if (ret < 0) + { + return ret; + } + + state->pkt_len = ret; + gdb_send_packet(state); + + return ret; +} + +/**************************************************************************** + * Name: gdb_write_memory + * + * Description: + * Write Memory Command Format: M addr,length:XX.. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * The number of bytes written if successful. + * Negative value on error. + * + * Note : Comand Format: MAAAAAAAAA,LLLLLLLL:XXXXXXXXX... + * Response Format: OK + ****************************************************************************/ + +static int gdb_write_memory(FAR struct gdb_state_s *state) +{ + uintptr_t addr; + size_t length; + int ret; + + state->pkt_next++; + ret = gdb_expect_addr_lenth(state, &addr, &length); + if (ret < 0) + { + return ret; + } + + ret = gdb_put_memory(state, state->pkt_next, gdb_remaining_len(state), + addr, length, gdb_hex2bin); + if (ret < 0) + { + return ret; + } + + gdb_send_ok_packet(state); + return ret; +} + +/**************************************************************************** + * Name: gdb_write_bin_memory + * + * Description: + * Write Memory (Binary) Command Format: X addr,length:XX.. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * The number of bytes written if successful. + * Negative value on error. + * + * Note : Comand Format: XAAAAAAAAA,LLLLLLLL:XXXXXXXXX... + * Response Format: OK + ****************************************************************************/ + +static int gdb_write_bin_memory(FAR struct gdb_state_s *state) +{ + uintptr_t addr; + size_t length; + int ret; + + state->pkt_next++; + ret = gdb_expect_addr_lenth(state, &addr, &length); + if (ret < 0) + { + return ret; + } + + ret = gdb_expect_seperator(state, ':'); + if (ret < 0) + { + return ret; + } + + ret = gdb_put_memory(state, state->pkt_next, gdb_remaining_len(state), + addr, length, gdb_bin2bin); + if (ret < 0) + { + return ret; + } + + gdb_send_ok_packet(state); + return ret; +} + +/**************************************************************************** + * Name: gdb_get_thread + * + * Description: + * Get the all thread id. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + ****************************************************************************/ + +static void gdb_get_thread(FAR struct tcb_s *tcb, FAR void *arg) +{ + FAR struct gdb_state_s *state = arg; + int pid = tcb->pid; + size_t len; + + len = sprintf(&state->pkt_buf[state->pkt_len], "%x,", pid); + state->pkt_len += len; +} + +/**************************************************************************** + * Name: gdb_query + * + * Description: + * The query packet is used by GDB to request information from the stub. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * 0 if successful. + * Negative value on error. + * + * Note : Comand Format: qSTRING + * STRING:is the query string. + ****************************************************************************/ + +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 r_cmd[] = "Rcmd"; + + if (memcmp(&state->pkt_buf[1], fthread_info, + sizeof(fthread_info) - 1) == 0) + { + state->pkt_buf[0] = 'm'; + state->pkt_len = 1; + nxsched_foreach(gdb_get_thread, state); + state->pkt_len--; + state->pkt_buf[state->pkt_len] = 0; + gdb_send_packet(state); + } + else if (memcmp(&state->pkt_buf[1], sthread_info, + sizeof(sthread_info) - 1) == 0) + { + state->pkt_buf[0] = 'l'; + state->pkt_len = 1; + gdb_send_packet(state); + } + else if (memcmp(&state->pkt_buf[1], thread_extra_info, + sizeof(thread_extra_info) - 1) == 0) + { + FAR struct tcb_s *tcb; + char thread_state[32]; + char thread_info[128]; + uintptr_t pid; + int ret; + + state->pkt_next += sizeof(thread_extra_info) + 1; + ret = gdb_expect_integer(state, &pid); + if (ret < 0) + { + return ret; + } + + tcb = nxsched_get_tcb(pid); + if (tcb == NULL) + { + return -EINVAL; + } + + nxsched_get_stateinfo(tcb, thread_state, sizeof(thread_state)); +#if CONFIG_TASK_NAME_SIZE > 0 + snprintf(thread_info, sizeof(thread_info), + "Name: %s, State: %s, Priority: %d, Stack: %d", + tcb->name, thread_state, tcb->sched_priority, + tcb->adj_stack_size); +#else + snprintf(thread_info, sizeof(thread_info), + "State: %s, Priority: %d, Stack: %d", + thread_state, tcb->sched_priority, + tcb->adj_stack_size); +#endif + + ret = gdb_bin2hex(state->pkt_buf, sizeof(state->pkt_buf), + thread_info, strlen(thread_info)); + + state->pkt_len = ret; + gdb_send_packet(state); + } + else if (state->monitor != NULL && + memcmp(&state->pkt_buf[1], r_cmd, sizeof(r_cmd) - 1) == 0) + { + ssize_t len = state->pkt_len - sizeof(r_cmd) - 1; /* skip the 'Rcmd,' */ + char cmd[128]; + int ret; + + len = gdb_hex2bin(cmd, len / 2, + &state->pkt_buf[sizeof(r_cmd) + 1], len); + if (len < 0) + { + return len; + } + + cmd[len] = '\0'; + ret = state->monitor(state, cmd); + if (ret < 0) + { + return ret; + } + else + { + gdb_send_ok_packet(state); + } + } + else + { + return -EPROTONOSUPPORT; + } + + return 0; +} + +/**************************************************************************** + * Name: gdb_is_thread_active + * + * Description: + * The is thread active 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: T + * id:is the thread id. + * Rsponse Format: OK + ****************************************************************************/ + +static int gdb_is_thread_active(FAR struct gdb_state_s *state) +{ + FAR struct tcb_s *tcb; + uintptr_t pid; + int ret; + + state->pkt_next++; + ret = gdb_expect_integer(state, &pid); + if (ret < 0) + { + return ret; + } + + tcb = nxsched_get_tcb(pid); + if (tcb == NULL) + { + return -EINVAL; + } + + state->pid = pid; + gdb_send_ok_packet(state); + return ret; +} + +/**************************************************************************** + * Name: gdb_thread_context + * + * Description: + * The thread context packet is used by GDB to request information from + * the stub. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * 0 if successful. + * Negative value on error. + * + * Note : Comand Format: Hc + * Rsponse Format: OK + ****************************************************************************/ + +static int gdb_thread_context(FAR struct gdb_state_s *state) +{ + FAR struct tcb_s *tcb; + uintptr_t pid; + int ret; + + state->pkt_next += 2; + ret = gdb_expect_integer(state, &pid); + if (ret < 0) + { + return ret; + } + + tcb = nxsched_get_tcb(pid); + if (tcb == NULL) + { + return -EINVAL; + } + + state->pid = pid; + gdb_send_ok_packet(state); + return 0; +} + +/**************************************************************************** + * Name: gdb_send_stop + * + * Description: + * The stop packet is used by GDB to request information from the stub. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * 0 if successful. + * Negative value on error. + * + ****************************************************************************/ + +static int gdb_send_stop(FAR struct gdb_state_s *state) +{ + return gdb_send_signal_packet(state, 0x00); +} + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +extern const struct tcbinfo_s g_tcbinfo; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: gdb_state_init + * + * Description: + * Initialize the GDB state structure. + * + * Input Parameters: + * send - The pointer to the send function. + * recv - The pointer to the receive function. + * monitor - The pointer to the monitor_cmd function. + * priv - The pointer to the private data. + * + * Returned Value: + * The pointer to the GDB state structure on success. + * NULL on failure. + * + ****************************************************************************/ + +FAR struct gdb_state_s *gdb_state_init(gdb_send_func_t send, + gdb_recv_func_t recv, + gdb_monitor_func_t monitor, + FAR void *priv) +{ + size_t size = g_tcbinfo.basic_num * sizeof(uintptr_t); + FAR struct gdb_state_s *state = lib_zalloc(sizeof(*state) + size); + + if (state == NULL) + { + return NULL; + } + + state->size = size; + state->send = send; + state->recv = recv; + state->priv = priv; + state->monitor = monitor; + + return state; +} + +/**************************************************************************** + * Name: gdb_state_uninit + * + * Description: + * Uninitialize the GDB state structure. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + ****************************************************************************/ + +void gdb_state_uninit(FAR struct gdb_state_s *state) +{ + if (state != NULL) + { + lib_free(state); + } +} + +/**************************************************************************** + * Name: gdb_console_message + * + * Description: + * Send a message to the GDB console (via O XX... packet). + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * Zero on success. + * Negative value on error. + * + ****************************************************************************/ + +int gdb_console_message(FAR struct gdb_state_s *state, FAR const char *msg) +{ + ssize_t ret; + + if (state == NULL || msg == NULL) + { + return -EINVAL; + } + + state->pkt_buf[0] = 'O'; + ret = gdb_bin2hex(&state->pkt_buf[1], sizeof(state->pkt_buf) - 1, + msg, strlen(msg)); + if (ret < 0) + { + return ret; + } + + state->pkt_len = 1 + ret; + return gdb_send_packet(state); +} + +/**************************************************************************** + * Name: gdb_process + * + * Description: + * Main debug loop. Handles commands. + * + * Input Parameters: + * state - The pointer to the GDB state structure. + * + * Returned Value: + * Zero if successful. + * Negative value on error. + * + ****************************************************************************/ + +int gdb_process(FAR struct gdb_state_s *state) +{ + int ret; + + while ((ret = gdb_recv_packet(state)) >= 0) + { + /* Handle one letter commands */ + + switch (state->pkt_buf[0]) + { + case '?': /* gdbserial status */ + ret = gdb_send_stop(state); + break; + case 'g': /* Read registers */ + ret = gdb_read_registers(state); + break; + case 'G': /* Write registers */ + ret = gdb_write_registers(state); + break; + case 'p': /* Read one register */ + ret = gdb_read_register(state); + break; + case 'm': /* Read memory */ + ret = gdb_read_memory(state); + break; + case 'M': /* Write memory */ + ret = gdb_write_memory(state); + break; + case 'X': /* Write binary memory */ + ret = gdb_write_bin_memory(state); + break; + case 'q': /* Query command */ + ret = gdb_query(state); + break; + case 'T': /* Query thread */ + ret = gdb_is_thread_active(state); + break; + case 'H': /* Thread related */ + ret = gdb_thread_context(state); + break; + case 'k': /* Kill request */ + ret = -ECONNRESET; + break; + default: + ret = -EPROTONOSUPPORT; + } + + if (ret == -EPROTONOSUPPORT) + { + state->pkt_len = 0; + gdb_send_packet(state); + } + else if (ret == -ECONNRESET) + { + break; + } + else if (ret < 0) + { + gdb_send_error_packet(state, 0x00); + } + } + + return ret; +}