6b070b2b00
Signed-off-by: chao an <anchao@xiaomi.com>
1561 lines
40 KiB
C
1561 lines
40 KiB
C
/****************************************************************************
|
|
* 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 <ctype.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <syslog.h>
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <nuttx/sched.h>
|
|
#include <nuttx/gdbstub.h>
|
|
|
|
/****************************************************************************
|
|
* 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: $<packet-data>#<checksum>
|
|
*
|
|
* 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 ssize_t 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 == _SCHED_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: %zu",
|
|
tcb->name, thread_state, tcb->sched_priority,
|
|
tcb->adj_stack_size);
|
|
#else
|
|
snprintf(thread_info, sizeof(thread_info),
|
|
"State: %s, Priority: %d, Stack: %zu",
|
|
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>
|
|
* 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<id>
|
|
* 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;
|
|
}
|
|
|