mirror of
https://gitlab.rtems.org/rtems/rtos/rtems.git
synced 2025-12-05 15:15:44 +00:00
2058 lines
53 KiB
C
2058 lines
53 KiB
C
/*
|
|
* Copyright (c) 2016-2019 Chris Johns <chrisj@rtems.org>.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#define RTEMS_DEBUGGER_VERBOSE_LOCK 0
|
|
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <rtems/bspIo.h>
|
|
#include <rtems/score/smp.h>
|
|
|
|
#include <rtems/rtems-debugger.h>
|
|
#include <rtems/debugger/rtems-debugger-server.h>
|
|
#include <rtems/debugger/rtems-debugger-remote.h>
|
|
|
|
#include "rtems-debugger-target.h"
|
|
#include "rtems-debugger-threads.h"
|
|
|
|
/*
|
|
* GDB Debugger Remote Server for RTEMS.
|
|
*/
|
|
|
|
/*
|
|
* Hack to void including bsp.h. The reset needs a better API.
|
|
*/
|
|
extern void bsp_reset(void);
|
|
|
|
/*
|
|
* Command lookup table.
|
|
*/
|
|
typedef int (*rtems_debugger_command)(uint8_t* buffer, int size);
|
|
|
|
typedef struct rtems_debugger_packet
|
|
{
|
|
const char* const label;
|
|
rtems_debugger_command command;
|
|
} rtems_debugger_packet;
|
|
|
|
/**
|
|
* Common error strings.
|
|
*/
|
|
static const char* const r_OK = "OK";
|
|
static const char* const r_E01 = "E01";
|
|
|
|
/*
|
|
* Global Debugger.
|
|
*
|
|
* The server instance is allocated on the heap so memory is only used then the
|
|
* server is running. A global is used because:
|
|
*
|
|
* 1. There can only be a single instance at once.
|
|
* 2. The backend's need access to the data and holding pointers in the TCB
|
|
* for each thread is mess.
|
|
* 3. The code is smaller and faster.
|
|
*/
|
|
rtems_debugger_server* rtems_debugger;
|
|
|
|
/**
|
|
* Print lock ot make the prints sequential. This is to debug the debugger in
|
|
* SMP.
|
|
*/
|
|
RTEMS_INTERRUPT_LOCK_DEFINE(static, printk_lock, "printk_lock")
|
|
|
|
void
|
|
rtems_debugger_printk_lock(rtems_interrupt_lock_context* lock_context)
|
|
{
|
|
rtems_interrupt_lock_acquire(&printk_lock, lock_context);
|
|
}
|
|
|
|
void
|
|
rtems_debugger_printk_unlock(rtems_interrupt_lock_context* lock_context)
|
|
{
|
|
rtems_interrupt_lock_release(&printk_lock, lock_context);
|
|
}
|
|
|
|
int
|
|
rtems_debugger_clean_printf(const char* format, ...)
|
|
{
|
|
rtems_interrupt_lock_context lock_context;
|
|
int len;
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
rtems_debugger_printk_lock(&lock_context);
|
|
len = vprintk(format, ap);
|
|
rtems_debugger_printk_unlock(&lock_context);
|
|
va_end(ap);
|
|
return len;
|
|
}
|
|
|
|
int
|
|
rtems_debugger_printf(const char* format, ...)
|
|
{
|
|
rtems_interrupt_lock_context lock_context;
|
|
int len;
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
rtems_debugger_printk_lock(&lock_context);
|
|
printk("[CPU:%d] ", (int) _SMP_Get_current_processor ());
|
|
len = vprintk(format, ap);
|
|
rtems_debugger_printk_unlock(&lock_context);
|
|
va_end(ap);
|
|
return len;
|
|
}
|
|
|
|
bool
|
|
rtems_debugger_verbose(void)
|
|
{
|
|
return rtems_debugger_server_flag(RTEMS_DEBUGGER_FLAG_VERBOSE);
|
|
}
|
|
|
|
static inline int
|
|
hex_decode(uint8_t ch)
|
|
{
|
|
int i;
|
|
if (ch >= '0' && ch <= '9')
|
|
i = (int) (ch - '0');
|
|
else if (ch >= 'a' && ch <= 'f')
|
|
i = (int) (ch - 'a') + 10;
|
|
else if (ch >= 'A' && ch <= 'F')
|
|
i = (int) (ch - 'A') + 10;
|
|
else
|
|
i = -1;
|
|
return i;
|
|
}
|
|
|
|
static inline uint8_t
|
|
hex_encode(int val)
|
|
{
|
|
return "0123456789abcdef"[val & 0xf];
|
|
}
|
|
|
|
static inline uintptr_t
|
|
hex_decode_addr(const uint8_t* data)
|
|
{
|
|
uintptr_t ui = 0;
|
|
size_t i;
|
|
if (data[0] == '-') {
|
|
if (data[1] == '1')
|
|
ui = (uintptr_t) -1;
|
|
}
|
|
else {
|
|
for (i = 0; i < (sizeof(ui) * 2); ++i) {
|
|
int v = hex_decode(data[i]);
|
|
if (v < 0)
|
|
break;
|
|
ui = (ui << 4) | v;
|
|
}
|
|
}
|
|
return ui;
|
|
}
|
|
|
|
static inline DB_UINT
|
|
hex_decode_uint(const uint8_t* data)
|
|
{
|
|
DB_UINT ui = 0;
|
|
size_t i;
|
|
if (data[0] == '-') {
|
|
if (data[1] == '1')
|
|
ui = (DB_UINT) -1;
|
|
}
|
|
else {
|
|
for (i = 0; i < (sizeof(ui) * 2); ++i) {
|
|
int v = hex_decode(data[i]);
|
|
if (v < 0)
|
|
break;
|
|
ui = (ui << 4) | v;
|
|
}
|
|
}
|
|
return ui;
|
|
}
|
|
|
|
static inline int
|
|
hex_decode_int(const uint8_t* data)
|
|
{
|
|
return (int) hex_decode_uint(data);
|
|
}
|
|
|
|
static bool
|
|
thread_id_decode(const char* data, DB_UINT* pid, DB_UINT* tid)
|
|
{
|
|
bool is_extended = false;
|
|
if (*data == 'p') {
|
|
is_extended = true;
|
|
++data;
|
|
}
|
|
*pid = *tid = hex_decode_uint((const uint8_t*) data);
|
|
if (is_extended) {
|
|
const char* stop = strchr(data, '.');
|
|
if (stop != NULL) {
|
|
*tid = hex_decode_uint((const uint8_t*) stop + 1);
|
|
}
|
|
}
|
|
return is_extended;
|
|
}
|
|
|
|
static inline bool
|
|
check_pid(DB_UINT pid)
|
|
{
|
|
return pid == 0 || rtems_debugger->pid == (pid_t) pid;
|
|
}
|
|
|
|
void
|
|
rtems_debugger_lock(void)
|
|
{
|
|
_Mutex_recursive_Acquire(&rtems_debugger->lock);
|
|
}
|
|
|
|
void
|
|
rtems_debugger_unlock(void)
|
|
{
|
|
_Mutex_recursive_Release(&rtems_debugger->lock);
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_lock_create(void)
|
|
{
|
|
_Mutex_recursive_Initialize_named(&rtems_debugger->lock, "DBlock");
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_lock_destroy(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_task_create(const char* name,
|
|
rtems_task_priority priority,
|
|
size_t stack_size,
|
|
rtems_task_entry entry_point,
|
|
rtems_task_argument argument,
|
|
rtems_id* id)
|
|
{
|
|
rtems_name tname;
|
|
rtems_status_code sc;
|
|
|
|
tname = rtems_build_name(name[0], name[1], name[2], name[3]);
|
|
|
|
sc = rtems_task_create (tname,
|
|
priority,
|
|
stack_size,
|
|
RTEMS_PREEMPT | RTEMS_NO_ASR,
|
|
RTEMS_LOCAL | RTEMS_FLOATING_POINT,
|
|
id);
|
|
if (sc != RTEMS_SUCCESSFUL) {
|
|
*id = 0;
|
|
rtems_debugger_printf("error: rtems-db: thread create: %s: %s\n",
|
|
name, rtems_status_text(sc));
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
sc = rtems_task_start(*id, entry_point, argument);
|
|
if (sc != RTEMS_SUCCESSFUL) {
|
|
rtems_debugger_printf("error: rtems-db: thread start: %s: %s\n",
|
|
name, rtems_status_text(sc));
|
|
rtems_task_delete(*id);
|
|
*id = 0;
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_task_destroy(const char* name,
|
|
rtems_id id,
|
|
volatile bool* finished,
|
|
int timeout)
|
|
{
|
|
while (timeout) {
|
|
bool has_finished;
|
|
|
|
rtems_debugger_lock();
|
|
has_finished = *finished;
|
|
rtems_debugger_unlock();
|
|
|
|
if (has_finished)
|
|
break;
|
|
|
|
usleep(RTEMS_DEBUGGER_POLL_WAIT);
|
|
if (timeout < RTEMS_DEBUGGER_POLL_WAIT)
|
|
timeout = 0;
|
|
else
|
|
timeout -= RTEMS_DEBUGGER_POLL_WAIT;
|
|
}
|
|
|
|
if (timeout == 0) {
|
|
rtems_debugger_printf("rtems-db: %s not stopping, killing\n", name);
|
|
rtems_task_delete(id);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
rtems_debugger_server_running(void)
|
|
{
|
|
bool running;
|
|
rtems_debugger_lock();
|
|
running = rtems_debugger->server_running;
|
|
rtems_debugger_unlock();
|
|
return running;
|
|
}
|
|
|
|
rtems_debugger_remote*
|
|
rtems_debugger_remote_handle(void)
|
|
{
|
|
rtems_debugger_remote* remote;
|
|
rtems_debugger_lock();
|
|
remote = rtems_debugger->remote;
|
|
rtems_debugger_unlock();
|
|
return remote;
|
|
}
|
|
|
|
bool
|
|
rtems_debugger_connected(void)
|
|
{
|
|
bool isconnected = false;
|
|
rtems_debugger_lock();
|
|
if (rtems_debugger->remote != NULL)
|
|
isconnected = rtems_debugger->remote->isconnected(rtems_debugger->remote);
|
|
rtems_debugger_unlock();
|
|
return isconnected;
|
|
}
|
|
|
|
bool
|
|
rtems_debugger_server_events_running(void)
|
|
{
|
|
return rtems_debugger->events_running;
|
|
}
|
|
|
|
void
|
|
rtems_debugger_server_events_signal(void)
|
|
{
|
|
_Condition_Signal(&rtems_debugger->server_cond);
|
|
}
|
|
|
|
static void
|
|
rtems_debugger_server_events_wait(void)
|
|
{
|
|
_Condition_Wait_recursive(&rtems_debugger->server_cond, &rtems_debugger->lock);
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_remote_connect(void)
|
|
{
|
|
rtems_debugger_remote* remote = rtems_debugger_remote_handle();
|
|
if (remote != NULL) {
|
|
if (!remote->isconnected(remote))
|
|
return remote->connect(remote);
|
|
}
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_remote_disconnect(void)
|
|
{
|
|
rtems_debugger_remote* remote = rtems_debugger_remote_handle();
|
|
if (remote != NULL) {
|
|
if (remote->isconnected(remote))
|
|
return remote->disconnect(remote);
|
|
}
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_remote_receive(uint8_t* buffer, size_t size)
|
|
{
|
|
rtems_debugger_remote* remote = rtems_debugger_remote_handle();
|
|
ssize_t len = remote->read(remote, buffer, size);
|
|
if (len < 0 && errno != EAGAIN)
|
|
rtems_debugger_printf("rtems-db: read: (%d) %s\n",
|
|
errno, strerror(errno));
|
|
return (int) len;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_remote_send(void)
|
|
{
|
|
const uint8_t* buffer = rtems_debugger->output;
|
|
ssize_t size = rtems_debugger->output_level;
|
|
|
|
if (rtems_debugger->output_level > RTEMS_DEBUGGER_BUFFER_SIZE) {
|
|
rtems_debugger_printf("rtems-db: write too big: %d\n",
|
|
(int) rtems_debugger->output_level);
|
|
return -1;
|
|
}
|
|
|
|
if (rtems_debugger->remote_debug) {
|
|
size_t i = 0;
|
|
rtems_debugger_printf("rtems-db: put:%4zu: ", rtems_debugger->output_level);
|
|
while (i < rtems_debugger->output_level)
|
|
rtems_debugger_clean_printf("%c", (char) rtems_debugger->output[i++]);
|
|
rtems_debugger_clean_printf("\n");
|
|
}
|
|
|
|
while (size) {
|
|
rtems_debugger_remote* remote = rtems_debugger_remote_handle();
|
|
ssize_t w;
|
|
if (remote == NULL) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
w = remote->write(remote, buffer, size);
|
|
if (w < 0 && errno != EINTR) {
|
|
rtems_debugger_printf("rtems-db: write: (%d) %s\n",
|
|
errno, strerror(errno));
|
|
break;
|
|
}
|
|
else {
|
|
size -= w;
|
|
buffer += w;
|
|
}
|
|
}
|
|
|
|
return (int) rtems_debugger->output_level;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_remote_send_ack(void)
|
|
{
|
|
rtems_debugger->output[0] = '+';
|
|
rtems_debugger->output_level = 1;
|
|
return rtems_debugger_remote_send();
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_remote_send_nack(void)
|
|
{
|
|
rtems_debugger->output[0] = '-';
|
|
rtems_debugger->output_level = 1;
|
|
return rtems_debugger_remote_send();
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_remote_packet_in(void)
|
|
{
|
|
uint8_t buf[256];
|
|
uint8_t state;
|
|
int in = 0;
|
|
uint8_t csum = 0;
|
|
uint8_t rx_csum = 0;
|
|
bool junk = false;
|
|
bool escaped = false;
|
|
bool remote_debug_header = true;
|
|
|
|
/*
|
|
* States:
|
|
* 'H' : Looking for the start character '$', '-' or '+'.
|
|
* 'P' : Looking for the checksum character '#' else buffer data.
|
|
* '1' : Looking for the first checksum character.
|
|
* '2' : Looking for the second checksum character.
|
|
* 'F' : Finished.
|
|
*/
|
|
|
|
state = 'H';
|
|
|
|
while (state != 'F') {
|
|
int r;
|
|
int i;
|
|
|
|
rtems_debugger_unlock();
|
|
|
|
r = rtems_debugger_remote_receive(buf, sizeof(buf));
|
|
|
|
rtems_debugger_lock();
|
|
|
|
if (r <= 0) {
|
|
/*
|
|
* Timeout?
|
|
*/
|
|
if (r < 0 && errno == EAGAIN) {
|
|
if (rtems_debugger->ack_pending) {
|
|
rtems_debugger_remote_send();
|
|
}
|
|
continue;
|
|
}
|
|
if (r == 0)
|
|
rtems_debugger_printf("rtems-db: remote disconnected\n");
|
|
return -1;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (i < r) {
|
|
uint8_t c = buf[i++];
|
|
|
|
if (rtems_debugger->remote_debug && remote_debug_header) {
|
|
rtems_debugger_printf("rtems-db: get:%4d: ", r);
|
|
remote_debug_header = false;
|
|
}
|
|
|
|
if (rtems_debugger->remote_debug)
|
|
rtems_debugger_clean_printf("%c", c);
|
|
|
|
switch (state) {
|
|
case 'H':
|
|
switch (c) {
|
|
case '+':
|
|
if (rtems_debugger->remote_debug) {
|
|
rtems_debugger_clean_printf(" [[ACK%s]]\n",
|
|
rtems_debugger->ack_pending ? "" : "?");
|
|
remote_debug_header = true;
|
|
}
|
|
rtems_debugger->ack_pending = false;
|
|
break;
|
|
case '-':
|
|
if (rtems_debugger->remote_debug) {
|
|
rtems_debugger_clean_printf(" [[NACK]]\n");
|
|
remote_debug_header = true;
|
|
}
|
|
/*
|
|
* Resend.
|
|
*/
|
|
rtems_debugger_remote_send();
|
|
break;
|
|
case '$':
|
|
state = 'P';
|
|
csum = 0;
|
|
in = 0;
|
|
if (junk && rtems_debugger->remote_debug) {
|
|
rtems_debugger_clean_printf("\b [[junk dropped]]\nrtems-db: get: : $");
|
|
remote_debug_header = false;
|
|
}
|
|
break;
|
|
case '\x3':
|
|
if (rtems_debugger->remote_debug)
|
|
rtems_debugger_clean_printf("^C [[BREAK]]\n");
|
|
rtems_debugger->ack_pending = false;
|
|
rtems_debugger->input[0] = '^';
|
|
rtems_debugger->input[1] = 'C';
|
|
rtems_debugger->input[2] = '\0';
|
|
return 2;
|
|
default:
|
|
junk = true;
|
|
break;
|
|
}
|
|
break;
|
|
case 'P':
|
|
if (c == '{' && !escaped) {
|
|
escaped = true;
|
|
}
|
|
else if (c == '$' && !escaped) {
|
|
csum = 0;
|
|
in = 0;
|
|
if (rtems_debugger->remote_debug) {
|
|
rtems_debugger_clean_printf("\n");
|
|
remote_debug_header = true;
|
|
}
|
|
}
|
|
else if (c == '#' && !escaped) {
|
|
rtems_debugger->input[in] = '\0';
|
|
rx_csum = 0;
|
|
state = '1';
|
|
}
|
|
else {
|
|
if (in >= (RTEMS_DEBUGGER_BUFFER_SIZE - 1)) {
|
|
rtems_debugger_printf("rtems-db: input buffer overflow\n");
|
|
return -1;
|
|
}
|
|
csum += c;
|
|
rtems_debugger->input[in++] = c;
|
|
}
|
|
break;
|
|
case '1':
|
|
rx_csum = (rx_csum << 4) | (uint8_t) hex_decode(c);
|
|
state = '2';
|
|
break;
|
|
case '2':
|
|
rx_csum = (rx_csum << 4) | (uint8_t) hex_decode(c);
|
|
if (csum == rx_csum) {
|
|
state = 'F';
|
|
if (rtems_debugger->remote_debug)
|
|
rtems_debugger_clean_printf("\n");
|
|
rtems_debugger_remote_send_ack();
|
|
}
|
|
else {
|
|
if (rtems_debugger->remote_debug) {
|
|
rtems_debugger_clean_printf(" [[invalid checksum]]\n");
|
|
remote_debug_header = true;
|
|
rtems_debugger_remote_send_nack();
|
|
}
|
|
state = 'H';
|
|
}
|
|
break;
|
|
case 'F':
|
|
if (rtems_debugger->remote_debug)
|
|
rtems_debugger_clean_printf(" [[extra data: 0x%02x]]", (int) c);
|
|
break;
|
|
default:
|
|
rtems_debugger_printf("rtems-db: bad state\n");
|
|
rtems_debugger_remote_send_nack();
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return in;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_remote_packet_in_hex(uint8_t* addr,
|
|
const char* data,
|
|
size_t size)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < size; ++i) {
|
|
*addr = (hex_decode(*data++) << 4);
|
|
*addr++ |= hex_decode(*data++);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if KEEP_INCASE
|
|
static void
|
|
remote_packet_out_rewind(size_t size)
|
|
{
|
|
size_t i = 0;
|
|
while (rtems_debugger->output_level > 0 && i < size) {
|
|
if (rtems_debugger->output_level > 1) {
|
|
if (rtems_debugger->output[rtems_debugger->output_level - 1] == '}') {
|
|
--rtems_debugger->output_level;
|
|
}
|
|
}
|
|
--rtems_debugger->output_level;
|
|
--i;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
remote_packet_out_append_buffer(const char* buffer, size_t size)
|
|
{
|
|
size_t ol = rtems_debugger->output_level;
|
|
size_t i = 0;
|
|
while (i < size) {
|
|
char c = buffer[i++];
|
|
if (c == '#' || c == '$') {
|
|
if (rtems_debugger->output_level >= (RTEMS_DEBUGGER_BUFFER_SIZE - 1)) {
|
|
rtems_debugger->output_level = ol;
|
|
rtems_debugger_printf("rtems-db: output overflow\n");
|
|
return -1;
|
|
}
|
|
rtems_debugger->output[rtems_debugger->output_level++] = '}';
|
|
c ^= 0x20;
|
|
}
|
|
if (rtems_debugger->output_level >= (RTEMS_DEBUGGER_BUFFER_SIZE - 1)) {
|
|
rtems_debugger->output_level = ol;
|
|
rtems_debugger_printf("rtems-db: output overflow\n");
|
|
return -1;
|
|
}
|
|
rtems_debugger->output[rtems_debugger->output_level++] = c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_packet_out_append_hex(const uint8_t* data, size_t size)
|
|
{
|
|
size_t ol = rtems_debugger->output_level;
|
|
size_t i = 0;
|
|
while (i < size) {
|
|
uint8_t byte = data[i++];
|
|
if (rtems_debugger->output_level >= (RTEMS_DEBUGGER_BUFFER_SIZE - 2)) {
|
|
rtems_debugger->output_level = ol;
|
|
rtems_debugger_printf("rtems-db: output overflow\n");
|
|
return -1;
|
|
}
|
|
rtems_debugger->output[rtems_debugger->output_level++] = hex_encode(byte >> 4);
|
|
rtems_debugger->output[rtems_debugger->output_level++] = hex_encode(byte);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_packet_out_append_str(const char* str)
|
|
{
|
|
return remote_packet_out_append_buffer(str, strlen(str));
|
|
}
|
|
|
|
static int
|
|
remote_packet_out_append_vprintf(const char* fmt, va_list ap)
|
|
{
|
|
int len;
|
|
char buffer[64];
|
|
len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
|
|
return remote_packet_out_append_buffer(buffer, len);
|
|
}
|
|
|
|
static int
|
|
remote_packet_out_append(const char* fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int r;
|
|
va_start(ap, fmt);
|
|
r = remote_packet_out_append_vprintf(fmt, ap);
|
|
va_end(ap);
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
remote_packet_out_reset(void)
|
|
{
|
|
rtems_debugger->output_level = 1;
|
|
rtems_debugger->output[0] = '$';
|
|
}
|
|
|
|
static int
|
|
remote_packet_out_buffer(const char* buffer, size_t size)
|
|
{
|
|
remote_packet_out_reset();
|
|
return remote_packet_out_append_buffer(buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_packet_out_str(const char* str)
|
|
{
|
|
remote_packet_out_reset();
|
|
return remote_packet_out_append_buffer(str, strlen(str));
|
|
}
|
|
|
|
static int
|
|
remote_packet_out(const char* fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int r;
|
|
va_start(ap, fmt);
|
|
remote_packet_out_reset();
|
|
r = remote_packet_out_append_vprintf(fmt, ap);
|
|
va_end(ap);
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
remote_packet_out_send(void)
|
|
{
|
|
uint8_t csum = 0;
|
|
size_t i = 1;
|
|
|
|
if (rtems_debugger->output_level >= (RTEMS_DEBUGGER_BUFFER_SIZE - 3)) {
|
|
rtems_debugger_printf("rtems-db: output overflow\n");
|
|
return -1;
|
|
}
|
|
|
|
while (i < rtems_debugger->output_level) {
|
|
csum += rtems_debugger->output[i++];
|
|
}
|
|
|
|
rtems_debugger->output[rtems_debugger->output_level++] = '#';
|
|
rtems_debugger->output[rtems_debugger->output_level++] = hex_encode((csum >> 4) & 0xf);
|
|
rtems_debugger->output[rtems_debugger->output_level++] = hex_encode(csum & 0xf);
|
|
|
|
rtems_debugger->ack_pending = true;;
|
|
|
|
return rtems_debugger_remote_send();
|
|
}
|
|
|
|
static int
|
|
remote_packet_dispatch(const rtems_debugger_packet* packet,
|
|
size_t packets,
|
|
uint8_t* buffer,
|
|
int size)
|
|
{
|
|
const rtems_debugger_packet* p;
|
|
size_t i;
|
|
int r = -1;
|
|
for (i = 0, p = &packet[0]; i < packets; ++i, ++p) {
|
|
if (strncmp(p->label,
|
|
(const char*) &buffer[0],
|
|
strlen(p->label)) == 0) {
|
|
if (rtems_debugger_server_flag(RTEMS_DEBUGGER_FLAG_VERBOSE_CMDS))
|
|
rtems_debugger_printf("rtems-db: cmd: %s [%d] '%s'\n",
|
|
p->label, size, (const char*) buffer);
|
|
r = p->command(buffer, size);
|
|
break;
|
|
}
|
|
}
|
|
if (r < 0) {
|
|
remote_packet_out_buffer("", 0);
|
|
remote_packet_out_send();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_detach(uint8_t* buffer, int size)
|
|
{
|
|
remote_packet_out_str(r_OK);
|
|
remote_packet_out_send();
|
|
rtems_debugger_remote_disconnect();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_ut_features(uint8_t* buffer, int size)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
remote_ut_osdata(uint8_t* buffer, int size)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
static const rtems_debugger_packet uninterpreted_transfer[] = {
|
|
{ .label = "qXfer:features",
|
|
.command = remote_ut_features },
|
|
{ .label = "qXfer:osdata",
|
|
.command = remote_ut_osdata },
|
|
};
|
|
|
|
#define REMOTE_UNINTERPRETED_TRANSFERS \
|
|
RTEMS_DEBUGGER_NUMOF(uninterpreted_transfer)
|
|
|
|
static int
|
|
remote_gq_uninterpreted_transfer(uint8_t* buffer, int size)
|
|
{
|
|
return remote_packet_dispatch(uninterpreted_transfer,
|
|
REMOTE_UNINTERPRETED_TRANSFERS,
|
|
buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_gq_thread_info_subsequent(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
if (threads->next >= threads->current.level)
|
|
remote_packet_out_str("l");
|
|
else {
|
|
rtems_debugger_thread* current;
|
|
const char* format = "p%d.%08lx";
|
|
current = rtems_debugger_thread_current(threads);
|
|
remote_packet_out_str("m");
|
|
while (threads->next < threads->current.level) {
|
|
int r;
|
|
r = remote_packet_out_append(format,
|
|
rtems_debugger->pid,
|
|
current[threads->next].id);
|
|
if (r < 0)
|
|
break;
|
|
format = ",p%d.%08lx";
|
|
++threads->next;
|
|
}
|
|
}
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_gq_thread_info_first(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger->threads->next = 0;
|
|
return remote_gq_thread_info_subsequent(buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_gq_thread_extra_info(uint8_t* buffer, int size)
|
|
{
|
|
const char* comma;
|
|
remote_packet_out_reset();
|
|
comma = strchr((const char*) buffer, ',');
|
|
if (comma != NULL) {
|
|
DB_UINT pid = 0;
|
|
DB_UINT tid = 0;
|
|
bool extended;
|
|
extended = thread_id_decode(comma + 1, &pid, &tid);
|
|
if (extended || check_pid(pid)) {
|
|
int r;
|
|
r = rtems_debugger_thread_find_index(tid);
|
|
if (r >= 0) {
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
rtems_debugger_thread* current;
|
|
rtems_debugger_thread* thread;
|
|
char buf[128];
|
|
char str[32];
|
|
size_t l;
|
|
current = rtems_debugger_thread_current(threads);
|
|
thread = ¤t[r];
|
|
l = snprintf(buf, sizeof(buf),
|
|
"%4s (%08" PRIx32 "), ", thread->name, thread->id);
|
|
remote_packet_out_append_hex((const uint8_t*) buf, l);
|
|
l = snprintf(buf, sizeof(buf),
|
|
"priority(c:%3d r:%3d), ",
|
|
rtems_debugger_thread_current_priority(thread),
|
|
rtems_debugger_thread_real_priority(thread));
|
|
remote_packet_out_append_hex((const uint8_t*) buf, l);
|
|
l = snprintf(buf, sizeof(buf),
|
|
"stack(s:%6lu a:%p), ",
|
|
rtems_debugger_thread_stack_size(thread),
|
|
rtems_debugger_thread_stack_area(thread));
|
|
remote_packet_out_append_hex((const uint8_t*) buf, l);
|
|
rtems_debugger_thread_state_str(thread, str, sizeof(str));
|
|
l = snprintf(buf, sizeof(buf), "state(%s)", str);
|
|
remote_packet_out_append_hex((const uint8_t*) buf, l);
|
|
}
|
|
}
|
|
}
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_gq_supported(uint8_t* buffer, int size)
|
|
{
|
|
uint32_t capabilities = rtems_debugger_target_capabilities();
|
|
const char* p;
|
|
bool swbreak = false;
|
|
bool hwbreak = false;
|
|
bool vCont = false;
|
|
bool no_resumed = false;
|
|
bool multiprocess = false;
|
|
remote_packet_out("qSupported:PacketSize=%d;QNonStop-",
|
|
RTEMS_DEBUGGER_BUFFER_SIZE);
|
|
p = strchr((const char*) buffer, ':');
|
|
if (p != NULL)
|
|
++p;
|
|
while (p != NULL && *p != '\0') {
|
|
bool echo = false;
|
|
char* sc;
|
|
sc = strchr(p, ';');
|
|
if (sc != NULL) {
|
|
*sc++ = '\0';
|
|
}
|
|
if (strcmp(p, "swbreak+") == 0 &&
|
|
!swbreak && (capabilities & RTEMS_DEBUGGER_TARGET_CAP_SWBREAK) != 0) {
|
|
swbreak = true;
|
|
echo = true;
|
|
}
|
|
if (strcmp(p, "hwbreak+") == 0 &&
|
|
!hwbreak && (capabilities & RTEMS_DEBUGGER_TARGET_CAP_HWBREAK) != 0) {
|
|
hwbreak = true;
|
|
echo = true;
|
|
}
|
|
if (!vCont && strcmp(p, "vContSupported+") == 0) {
|
|
rtems_debugger->flags |= RTEMS_DEBUGGER_FLAG_VCONT;
|
|
vCont = true;
|
|
echo = true;
|
|
}
|
|
if (!no_resumed && strcmp(p, "no-resumed+") == 0) {
|
|
no_resumed = true;
|
|
echo = true;
|
|
}
|
|
if (!multiprocess && strcmp(p, "multiprocess+") == 0) {
|
|
rtems_debugger->flags |= RTEMS_DEBUGGER_FLAG_MULTIPROCESS;
|
|
multiprocess = true;
|
|
echo = true;
|
|
}
|
|
|
|
if (echo) {
|
|
remote_packet_out_append_str(";");
|
|
remote_packet_out_append_str(p);
|
|
}
|
|
else if (strncmp(p, "xmlRegisters", sizeof("xmlRegisters") - 1) == 0) {
|
|
/* ignore */
|
|
}
|
|
else {
|
|
remote_packet_out_append_str(";");
|
|
remote_packet_out_append_buffer(p, strlen(p) - 1);
|
|
remote_packet_out_append_str("-");
|
|
}
|
|
p = sc;
|
|
}
|
|
if (!swbreak && (capabilities & RTEMS_DEBUGGER_TARGET_CAP_SWBREAK) != 0) {
|
|
remote_packet_out_append_str("swbreak+;");
|
|
}
|
|
if (!hwbreak && (capabilities & RTEMS_DEBUGGER_TARGET_CAP_HWBREAK) != 0) {
|
|
remote_packet_out_append_str("hwbreak+;");
|
|
}
|
|
if (!vCont) {
|
|
remote_packet_out_append_str("vContSupported+;");
|
|
}
|
|
if (!no_resumed) {
|
|
remote_packet_out_append_str("no-resumed+;");
|
|
}
|
|
if (!multiprocess) {
|
|
remote_packet_out_append_str("multiprocess+;");
|
|
}
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_gq_attached(uint8_t* buffer, int size)
|
|
{
|
|
const char* response = "1";
|
|
const char* colon = strchr((const char*) buffer, ':');
|
|
if (colon != NULL) {
|
|
DB_UINT pid = hex_decode_uint((const uint8_t*) colon + 1);
|
|
if ((pid_t) pid != rtems_debugger->pid)
|
|
response = r_E01;
|
|
}
|
|
remote_packet_out_str(response);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static const rtems_debugger_packet general_query[] = {
|
|
{ .label = "qfThreadInfo",
|
|
.command = remote_gq_thread_info_first },
|
|
{ .label = "qsThreadInfo",
|
|
.command = remote_gq_thread_info_subsequent },
|
|
{ .label = "qThreadExtraInfo",
|
|
.command = remote_gq_thread_extra_info },
|
|
{ .label = "qSupported",
|
|
.command = remote_gq_supported },
|
|
{ .label = "qAttached",
|
|
.command = remote_gq_attached },
|
|
{ .label = "qXfer",
|
|
.command = remote_gq_uninterpreted_transfer },
|
|
};
|
|
|
|
#define REMOTE_GENERAL_QUERIES RTEMS_DEBUGGER_NUMOF(general_query)
|
|
|
|
static int
|
|
remote_general_query(uint8_t* buffer, int size)
|
|
{
|
|
return remote_packet_dispatch(general_query, REMOTE_GENERAL_QUERIES,
|
|
buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_gs_non_stop(uint8_t* buffer, int size)
|
|
{
|
|
const char* response = r_E01;
|
|
char* p = strchr((char*) buffer, ':');
|
|
if (p != NULL) {
|
|
++p;
|
|
response = r_OK;
|
|
if (*p == '0') {
|
|
rtems_debugger->flags &= ~RTEMS_DEBUGGER_FLAG_NON_STOP;
|
|
}
|
|
else if (*p == '1') {
|
|
rtems_debugger->flags |= RTEMS_DEBUGGER_FLAG_NON_STOP;
|
|
}
|
|
else
|
|
response = r_E01;
|
|
}
|
|
remote_packet_out_str(response);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static const rtems_debugger_packet general_set[] = {
|
|
{ .label = "QNonStop",
|
|
.command = remote_gs_non_stop },
|
|
};
|
|
|
|
#define REMOTE_GENERAL_SETS RTEMS_DEBUGGER_NUMOF(general_set)
|
|
|
|
static int
|
|
remote_general_set(uint8_t* buffer, int size)
|
|
{
|
|
return remote_packet_dispatch(general_set, REMOTE_GENERAL_SETS,
|
|
buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_v_stopped(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
if (threads->next >= threads->stopped.level)
|
|
remote_packet_out_str(r_OK);
|
|
else {
|
|
rtems_id* stopped;
|
|
remote_packet_out("T%02x", rtems_debugger->signal);
|
|
stopped = rtems_debugger_thread_stopped(threads);
|
|
while (threads->next < threads->stopped.level) {
|
|
int r;
|
|
r = remote_packet_out_append("thread:p%d.%08lx;",
|
|
rtems_debugger->pid,
|
|
stopped[threads->next]);
|
|
if (r < 0)
|
|
break;
|
|
++threads->next;
|
|
}
|
|
}
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_stop_reason(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger->threads->next = 0;
|
|
return remote_v_stopped(buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_v_continue(uint8_t* buffer, int size)
|
|
{
|
|
buffer += 5;
|
|
|
|
if (buffer[0] == '?') {
|
|
/*
|
|
* You need to supply 'c' and 'C' or GDB says vCont is not supported. As
|
|
* Sammy-J says "Silly GDB".
|
|
*/
|
|
remote_packet_out_str("vCont;c;C;s;r;");
|
|
}
|
|
else {
|
|
const char* semi = (const char*) &buffer[0];
|
|
bool resume = false;
|
|
bool ok = true;
|
|
while (ok && semi != NULL) {
|
|
const char* colon = strchr(semi + 1, ':');
|
|
const char action = *(semi + 1);
|
|
DB_UINT pid = 0;
|
|
DB_UINT tid = 0;
|
|
bool extended;
|
|
if (colon != NULL) {
|
|
int r = -1;
|
|
extended = thread_id_decode(colon + 1, &pid, &tid);
|
|
if (extended || check_pid(pid)) {
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
rtems_debugger_thread* thread = NULL;
|
|
int index = 0;
|
|
if (tid != (DB_UINT) -1) {
|
|
rtems_debugger_thread* current;
|
|
current = rtems_debugger_thread_current(threads);
|
|
index = rtems_debugger_thread_find_index(tid);
|
|
if (index >= 0)
|
|
thread = ¤t[index];
|
|
}
|
|
switch (action) {
|
|
case 'c':
|
|
case 'C':
|
|
if (tid == (DB_UINT) -1) {
|
|
r = rtems_debugger_thread_continue_all();
|
|
}
|
|
else if (thread != NULL) {
|
|
r = rtems_debugger_thread_continue(thread);
|
|
}
|
|
if (r == 0)
|
|
resume = true;
|
|
break;
|
|
case 'S':
|
|
case 's':
|
|
if (thread != NULL) {
|
|
r = rtems_debugger_thread_step(thread);
|
|
if (r == 0)
|
|
resume = true;
|
|
}
|
|
break;
|
|
case 'r':
|
|
/*
|
|
* Range to step around inside: `r start,end`.
|
|
*/
|
|
if (thread != NULL) {
|
|
const char* comma;
|
|
comma = strchr(semi + 2, ',');
|
|
if (comma != NULL) {
|
|
DB_UINT start;
|
|
DB_UINT end;
|
|
start = hex_decode_uint((const uint8_t*) semi + 2);
|
|
end = hex_decode_uint((const uint8_t*) comma + 1);
|
|
r = rtems_debugger_thread_stepping(thread, start, end);
|
|
if (r == 0)
|
|
resume = true;
|
|
}
|
|
else {
|
|
ok = false;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
rtems_debugger_printf("rtems-db: vCont: unkown action: %c\n", action);
|
|
ok = false;
|
|
break;
|
|
}
|
|
if (r < 0)
|
|
ok = false;
|
|
}
|
|
}
|
|
else {
|
|
rtems_debugger_printf("rtems-db: vCont: no colon\n");
|
|
ok = false;
|
|
}
|
|
semi = strchr(semi + 1, ';');
|
|
}
|
|
|
|
if (ok)
|
|
remote_packet_out_str(r_OK);
|
|
else
|
|
remote_packet_out_str(r_E01);
|
|
|
|
if (resume)
|
|
rtems_debugger_thread_system_resume(false);
|
|
}
|
|
|
|
remote_packet_out_send();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_v_kill(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger->flags |= RTEMS_DEBUGGER_FLAG_RESET;
|
|
return remote_detach(buffer, size);
|
|
}
|
|
|
|
static const rtems_debugger_packet v_packets[] = {
|
|
{ .label = "vCont",
|
|
.command = remote_v_continue },
|
|
{ .label = "vStopped",
|
|
.command = remote_v_stopped },
|
|
{ .label = "vKill",
|
|
.command = remote_v_kill },
|
|
};
|
|
|
|
#define REMOTE_V_PACKETS RTEMS_DEBUGGER_NUMOF(v_packets)
|
|
|
|
static int
|
|
remote_v_packets(uint8_t* buffer, int size)
|
|
{
|
|
return remote_packet_dispatch(v_packets, REMOTE_V_PACKETS,
|
|
buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_thread_select(uint8_t* buffer, int size)
|
|
{
|
|
const char* response = r_OK;
|
|
int* index = NULL;
|
|
|
|
if (buffer[1] == 'g')
|
|
index = &rtems_debugger->threads->selector_gen;
|
|
else if (buffer[1] == 'c')
|
|
index = &rtems_debugger->threads->selector_cont;
|
|
else
|
|
response = r_E01;
|
|
|
|
if (index != NULL) {
|
|
DB_UINT pid = 0;
|
|
DB_UINT tid = 0;
|
|
bool extended;
|
|
extended = thread_id_decode((const char*) &buffer[2], &pid, &tid);
|
|
if (extended && !check_pid(pid)) {
|
|
response = r_E01;
|
|
}
|
|
else {
|
|
if (tid == 0 || tid == (DB_UINT) -1)
|
|
*index = (int) tid;
|
|
else {
|
|
int r;
|
|
r = rtems_debugger_thread_find_index(tid);
|
|
if (r < 0) {
|
|
response = r_E01;
|
|
*index = -1;
|
|
}
|
|
else
|
|
*index = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
remote_packet_out_str(response);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_thread_alive(uint8_t* buffer, int size)
|
|
{
|
|
const char* response = r_E01;
|
|
DB_UINT pid = 0;
|
|
DB_UINT tid = 0;
|
|
bool extended;
|
|
extended = thread_id_decode((const char*) &buffer[1], &pid, &tid);
|
|
if (!extended || (extended && check_pid(pid))) {
|
|
int r;
|
|
r = rtems_debugger_thread_find_index(tid);
|
|
if (r >= 0)
|
|
response = r_OK;
|
|
}
|
|
remote_packet_out_str(response);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_argc_argv(uint8_t* buffer, int size)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
remote_continue_at(uint8_t* buffer, int size)
|
|
{
|
|
if (!rtems_debugger_server_flag(RTEMS_DEBUGGER_FLAG_VCONT)) {
|
|
char* vCont_c = "vCont;c:p1.-1";
|
|
return remote_v_continue((uint8_t*) vCont_c, strlen(vCont_c));
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
remote_read_general_regs(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
bool ok = false;
|
|
int r;
|
|
if (threads->selector_gen >= 0 &&
|
|
threads->selector_gen < (int) threads->current.level) {
|
|
rtems_debugger_thread* current;
|
|
rtems_debugger_thread* thread;
|
|
current = rtems_debugger_thread_current(threads);
|
|
thread = ¤t[threads->selector_gen];
|
|
r = rtems_debugger_target_read_regs(thread);
|
|
if (r >= 0) {
|
|
remote_packet_out_reset();
|
|
r = remote_packet_out_append_hex((const uint8_t*) &thread->registers[0],
|
|
rtems_debugger_target_reg_table_size());
|
|
if (r >= 0)
|
|
ok = true;
|
|
}
|
|
}
|
|
if (!ok)
|
|
remote_packet_out_str(r_E01);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_write_general_regs(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
size_t reg_table_size = rtems_debugger_target_reg_table_size();
|
|
bool ok = false;
|
|
int r;
|
|
if (threads->selector_gen >= 0 &&
|
|
threads->selector_gen < (int) threads->current.level &&
|
|
((size - 1) / 2) == (int) reg_table_size) {
|
|
rtems_debugger_thread* current;
|
|
rtems_debugger_thread* thread;
|
|
current = rtems_debugger_thread_current(threads);
|
|
thread = ¤t[threads->selector_gen];
|
|
r = rtems_debugger_target_read_regs(thread);
|
|
if (r >= 0) {
|
|
r = rtems_debugger_remote_packet_in_hex((uint8_t*) &thread->registers[0],
|
|
(const char*) &buffer[1],
|
|
reg_table_size);
|
|
if (r >= 0) {
|
|
thread->flags |= RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY;
|
|
ok = true;
|
|
}
|
|
}
|
|
}
|
|
if (!ok)
|
|
remote_packet_out_str(r_E01);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_read_reg(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
bool ok = false;
|
|
int r;
|
|
if (threads->selector_gen >= 0
|
|
&& threads->selector_gen < (int) threads->current.level) {
|
|
size_t reg = hex_decode_int(&buffer[1]);
|
|
if (reg < rtems_debugger_target_reg_num()) {
|
|
rtems_debugger_thread* current;
|
|
rtems_debugger_thread* thread;
|
|
current = rtems_debugger_thread_current(threads);
|
|
thread = ¤t[threads->selector_gen];
|
|
r = rtems_debugger_target_read_regs(thread);
|
|
if (r >= 0) {
|
|
const size_t reg_size = rtems_debugger_target_reg_size(reg);
|
|
const size_t reg_offset = rtems_debugger_target_reg_offset(reg);
|
|
const uint8_t* addr = &thread->registers[reg_offset];
|
|
remote_packet_out_reset();
|
|
r = remote_packet_out_append_hex(addr, reg_size);
|
|
if (r >= 0)
|
|
ok = true;
|
|
}
|
|
}
|
|
}
|
|
if (!ok)
|
|
remote_packet_out_str(r_E01);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_write_reg(uint8_t* buffer, int size)
|
|
{
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
const char* response = r_E01;
|
|
if (threads->selector_gen >= 0
|
|
&& threads->selector_gen < (int) threads->current.level) {
|
|
const char* equals;
|
|
equals = strchr((const char*) buffer, '=');
|
|
if (equals != NULL) {
|
|
size_t reg = hex_decode_int(&buffer[1]);
|
|
if (reg < rtems_debugger_target_reg_num()) {
|
|
rtems_debugger_thread* current;
|
|
rtems_debugger_thread* thread;
|
|
int r;
|
|
current = rtems_debugger_thread_current(threads);
|
|
thread = ¤t[threads->selector_gen];
|
|
r = rtems_debugger_target_read_regs(thread);
|
|
if (r >= 0) {
|
|
const size_t reg_size = rtems_debugger_target_reg_size(reg);
|
|
const size_t reg_offset = rtems_debugger_target_reg_offset(reg);
|
|
uint8_t* addr = &thread->registers[reg_offset];
|
|
r = rtems_debugger_remote_packet_in_hex(addr, equals + 1, reg_size);
|
|
if (r == 0) {
|
|
thread->flags |= RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY;
|
|
response = r_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
remote_packet_out_str(response);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_read_memory(uint8_t* buffer, int size)
|
|
{
|
|
const char* comma;
|
|
comma = strchr((const char*) buffer, ',');
|
|
if (comma == NULL)
|
|
remote_packet_out_str(r_E01);
|
|
else {
|
|
uintptr_t addr;
|
|
DB_UINT length;
|
|
int r;
|
|
addr = hex_decode_addr(&buffer[1]);
|
|
length = hex_decode_uint((const uint8_t*) comma + 1);
|
|
remote_packet_out_reset();
|
|
r = rtems_debugger_target_start_memory_access();
|
|
if (r == 0) {
|
|
/*
|
|
* There should be specific target access for 8, 16, 32 and 64 bit reads.
|
|
*/
|
|
r = remote_packet_out_append_hex((const uint8_t*) addr, length);
|
|
}
|
|
rtems_debugger_target_end_memory_access();
|
|
if (r < 0)
|
|
remote_packet_out_str(r_E01);
|
|
}
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_write_memory(uint8_t* buffer, int size)
|
|
{
|
|
const char* response = r_E01;
|
|
const char* comma;
|
|
const char* colon;
|
|
comma = strchr((const char*) buffer, ',');
|
|
colon = strchr((const char*) buffer, ':');
|
|
if (comma != NULL && colon != NULL) {
|
|
uintptr_t addr;
|
|
DB_UINT length;
|
|
int r;
|
|
addr = hex_decode_addr(&buffer[1]);
|
|
length = hex_decode_uint((const uint8_t*) comma + 1);
|
|
r = rtems_debugger_target_start_memory_access();
|
|
if (r == 0) {
|
|
r = rtems_debugger_remote_packet_in_hex((uint8_t*) addr,
|
|
colon + 1,
|
|
length);
|
|
}
|
|
rtems_debugger_target_end_memory_access();
|
|
if (r == 0)
|
|
response = r_OK;
|
|
}
|
|
remote_packet_out_str(response);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_single_step(uint8_t* buffer, int size)
|
|
{
|
|
if (!rtems_debugger_server_flag(RTEMS_DEBUGGER_FLAG_VCONT)) {
|
|
rtems_debugger_threads* threads = rtems_debugger->threads;
|
|
if (threads != NULL && rtems_debugger_thread_current(threads) != NULL) {
|
|
rtems_debugger_thread* current;
|
|
char vCont_s[32];
|
|
current = rtems_debugger_thread_current(threads);
|
|
snprintf(vCont_s, sizeof(vCont_s), "vCont;s:p1.%08" PRIx32 ";c:p1.-1",
|
|
current[threads->selector_cont].id);
|
|
return remote_v_continue((uint8_t*) vCont_s, strlen(vCont_s));
|
|
}
|
|
remote_packet_out_str(r_E01);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
remote_breakpoints(bool insert, uint8_t* buffer, int size)
|
|
{
|
|
const char* comma1;
|
|
int r = -1;
|
|
comma1 = strchr((const char*) buffer, ',');
|
|
if (comma1 != NULL) {
|
|
const char* comma2;
|
|
comma2 = strchr(comma1 + 1, ',');
|
|
if (comma2 != NULL) {
|
|
uint32_t capabilities;
|
|
uintptr_t addr;
|
|
DB_UINT kind;
|
|
addr = hex_decode_addr((const uint8_t*) comma1 + 1);
|
|
kind = hex_decode_uint((const uint8_t*) comma2 + 1);
|
|
capabilities = rtems_debugger_target_capabilities();
|
|
switch (buffer[1]) {
|
|
case '0':
|
|
if ((capabilities & RTEMS_DEBUGGER_TARGET_CAP_SWBREAK) != 0) {
|
|
r = rtems_debugger_target_swbreak_control(insert, addr, kind);
|
|
}
|
|
break;
|
|
case '1': /* execute */
|
|
case '2': /* write */
|
|
case '3': /* read */
|
|
case '4': /* access */
|
|
if ((capabilities & RTEMS_DEBUGGER_TARGET_CAP_HWWATCH) != 0) {
|
|
rtems_debugger_target_watchpoint type;
|
|
switch (buffer[1]) {
|
|
case '1':
|
|
type = rtems_debugger_target_hw_execute;
|
|
break;
|
|
case '2':
|
|
type = rtems_debugger_target_hw_write;
|
|
break;
|
|
case '3':
|
|
type = rtems_debugger_target_hw_read;
|
|
break;
|
|
case '4':
|
|
default:
|
|
type = rtems_debugger_target_hw_read_write;
|
|
break;
|
|
}
|
|
r = rtems_debugger_target_hwbreak_control(type, insert, addr, kind);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
remote_packet_out_str(r < 0 ? r_E01 : r_OK);
|
|
remote_packet_out_send();
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
remote_insert_breakpoint(uint8_t* buffer, int size)
|
|
{
|
|
return remote_breakpoints(true, buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_remove_breakpoint(uint8_t* buffer, int size)
|
|
{
|
|
return remote_breakpoints(false, buffer, size);
|
|
}
|
|
|
|
static int
|
|
remote_break(uint8_t* buffer, int size)
|
|
{
|
|
int r;
|
|
r = rtems_debugger_thread_system_suspend();
|
|
if (r < 0) {
|
|
rtems_debugger_printf("error: rtems-db: suspend all on break\n");
|
|
}
|
|
return remote_stop_reason(buffer, size);
|
|
}
|
|
|
|
static const rtems_debugger_packet packets[] = {
|
|
{ .label = "q",
|
|
.command = remote_general_query },
|
|
{ .label = "Q",
|
|
.command = remote_general_set },
|
|
{ .label = "v",
|
|
.command = remote_v_packets },
|
|
{ .label = "H",
|
|
.command = remote_thread_select },
|
|
{ .label = "T",
|
|
.command = remote_thread_alive },
|
|
{ .label = "?",
|
|
.command = remote_stop_reason },
|
|
{ .label = "A",
|
|
.command = remote_argc_argv },
|
|
{ .label = "c",
|
|
.command = remote_continue_at },
|
|
{ .label = "g",
|
|
.command = remote_read_general_regs },
|
|
{ .label = "G",
|
|
.command = remote_write_general_regs },
|
|
{ .label = "p",
|
|
.command = remote_read_reg },
|
|
{ .label = "P",
|
|
.command = remote_write_reg },
|
|
{ .label = "m",
|
|
.command = remote_read_memory },
|
|
{ .label = "M",
|
|
.command = remote_write_memory },
|
|
{ .label = "s",
|
|
.command = remote_single_step },
|
|
{ .label = "Z",
|
|
.command = remote_insert_breakpoint },
|
|
{ .label = "z",
|
|
.command = remote_remove_breakpoint },
|
|
{ .label = "D",
|
|
.command = remote_detach },
|
|
{ .label = "k",
|
|
.command = remote_v_kill },
|
|
{ .label = "r",
|
|
.command = remote_v_kill },
|
|
{ .label = "R",
|
|
.command = remote_v_kill },
|
|
{ .label = "^C",
|
|
.command = remote_break },
|
|
};
|
|
|
|
#define REMOTE_PACKETS RTEMS_DEBUGGER_NUMOF(packets)
|
|
|
|
static int
|
|
remote_packets(uint8_t* buffer, size_t size)
|
|
{
|
|
return remote_packet_dispatch(packets, REMOTE_PACKETS,
|
|
buffer, size);
|
|
}
|
|
|
|
static void
|
|
rtems_debugger_events(rtems_task_argument arg)
|
|
{
|
|
int r = 0;
|
|
|
|
if (rtems_debugger_verbose())
|
|
rtems_debugger_printf("rtems-db: events running\n");
|
|
|
|
/*
|
|
* Hold the lock until the thread blocks waiting for an event.
|
|
*/
|
|
rtems_debugger_lock();
|
|
|
|
rtems_debugger_target_enable();
|
|
|
|
if (rtems_debugger_server_flag(RTEMS_DEBUGGER_FLAG_BREAK_WAITER)) {
|
|
rtems_debugger->flags &= ~RTEMS_DEBUGGER_FLAG_BREAK_WAITER;
|
|
r = rtems_debugger_thread_system_suspend();
|
|
if (rtems_debugger_verbose())
|
|
rtems_debugger_printf("rtems-db: break waiter\n");
|
|
rtems_debugger_server_events_signal();
|
|
if (rtems_debugger_verbose())
|
|
rtems_debugger_printf("rtems-db: break waiter: signalled\n");
|
|
}
|
|
|
|
if (r == 0) {
|
|
while (rtems_debugger_server_events_running()) {
|
|
rtems_debugger_server_events_wait();
|
|
if (rtems_debugger_verbose())
|
|
rtems_debugger_printf("rtems-db: event woken\n");
|
|
if (!rtems_debugger_server_events_running())
|
|
break;
|
|
r = rtems_debugger_thread_system_suspend();
|
|
if (r < 0)
|
|
break;
|
|
r = remote_stop_reason(NULL, 0);
|
|
if (r < 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (r < 0)
|
|
rtems_debugger_printf("rtems-db: error in events\n");
|
|
|
|
rtems_debugger_target_disable();
|
|
|
|
rtems_debugger->events_running = false;
|
|
rtems_debugger->events_finished = true;
|
|
|
|
rtems_debugger_unlock();
|
|
|
|
if (rtems_debugger_verbose())
|
|
rtems_debugger_printf("rtems-db: events finishing\n");
|
|
|
|
rtems_task_exit();
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_session(void)
|
|
{
|
|
int r;
|
|
int rr;
|
|
|
|
if (rtems_debugger_verbose())
|
|
rtems_debugger_printf("rtems-db: remote running\n");
|
|
|
|
/*
|
|
* Hold the lock until the thread blocks on the remote input.
|
|
*/
|
|
rtems_debugger_lock();
|
|
|
|
r = rtems_debugger_target_create();
|
|
if (r < 0) {
|
|
rtems_debugger_unlock();
|
|
return r;
|
|
}
|
|
|
|
r = rtems_debugger_thread_create();
|
|
if (r < 0) {
|
|
rtems_debugger_target_destroy();
|
|
rtems_debugger_unlock();
|
|
return r;
|
|
}
|
|
|
|
rtems_debugger->events_running = true;
|
|
rtems_debugger->events_finished = false;
|
|
|
|
r = rtems_debugger_task_create("DBSe",
|
|
rtems_debugger->priority,
|
|
RTEMS_DEBUGGER_STACKSIZE,
|
|
rtems_debugger_events,
|
|
0,
|
|
&rtems_debugger->events_task);
|
|
if (r < 0) {
|
|
rtems_debugger_thread_destroy();
|
|
rtems_debugger_target_destroy();
|
|
rtems_debugger_unlock();
|
|
return r;
|
|
}
|
|
|
|
while (rtems_debugger_server_running() &&
|
|
rtems_debugger_connected()) {
|
|
r = rtems_debugger_remote_packet_in();
|
|
if (r < 0)
|
|
break;
|
|
if (r > 0) {
|
|
remote_packets(&rtems_debugger->input[0], r);
|
|
}
|
|
}
|
|
|
|
rtems_debugger->events_running = false;
|
|
rtems_debugger_server_events_signal();
|
|
|
|
rtems_debugger_unlock();
|
|
|
|
rr = rtems_debugger_task_destroy("DBSe",
|
|
rtems_debugger->events_task,
|
|
&rtems_debugger->events_finished,
|
|
RTEMS_DEBUGGER_TIMEOUT_STOP);
|
|
if (rr < 0 && r == 0)
|
|
r = rr;
|
|
|
|
rtems_debugger_lock();
|
|
|
|
rr = rtems_debugger_target_destroy();
|
|
if (rr < 0 && r == 0)
|
|
r = rr;
|
|
|
|
rr = rtems_debugger_thread_destroy();
|
|
if (rr < 0 && r == 0)
|
|
r = rr;
|
|
|
|
if (rtems_debugger_server_flag(RTEMS_DEBUGGER_FLAG_RESET)) {
|
|
rtems_debugger_printf("rtems-db: shutdown\n");
|
|
/*
|
|
* Wait a moment to let any disconnection protocol a transport has
|
|
* complete. The legacy stack needs this to close the connection
|
|
* to GDB and clean up.
|
|
*/
|
|
sleep(2);
|
|
/*
|
|
* No special exit code, the user will asked GDB to kill the
|
|
* target
|
|
*/
|
|
rtems_fatal_error_occurred(1122);
|
|
}
|
|
|
|
rtems_debugger->flags = 0;
|
|
rtems_debugger->ack_pending = false;
|
|
|
|
rtems_debugger_unlock();
|
|
|
|
if (rtems_debugger_verbose())
|
|
rtems_debugger_printf("rtems-db: remote finishing\n");
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_create(const char* remote,
|
|
const char* device,
|
|
rtems_task_priority priority,
|
|
int timeout,
|
|
const rtems_printer* printer)
|
|
{
|
|
int r;
|
|
|
|
if (rtems_debugger != NULL) {
|
|
rtems_printf(printer, "error: rtems-db: create: already active\n");
|
|
errno = EEXIST;
|
|
return -1;
|
|
}
|
|
|
|
rtems_debugger = malloc(sizeof(rtems_debugger_server));
|
|
if (rtems_debugger == NULL) {
|
|
rtems_printf(printer, "error: rtems-db: create: no memory\n");
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
memset(rtems_debugger, 0, sizeof(rtems_debugger_server));
|
|
|
|
/*
|
|
* These do not change with a session.
|
|
*/
|
|
rtems_debugger->priority = priority;
|
|
rtems_debugger->timeout = timeout;
|
|
rtems_debugger->printer = *printer;
|
|
rtems_debugger->pid = getpid();
|
|
rtems_debugger->remote_debug = false;
|
|
|
|
rtems_chain_initialize_empty(&rtems_debugger->exception_threads);
|
|
|
|
rtems_debugger->remote = rtems_debugger_remote_find(remote);
|
|
if (rtems_debugger->remote== NULL) {
|
|
rtems_printf(printer, "error: rtems-db: remote not found: %s\n", remote);
|
|
free(rtems_debugger);
|
|
rtems_debugger = NULL;
|
|
return -1;
|
|
}
|
|
|
|
r = rtems_debugger->remote->begin(rtems_debugger->remote, device);
|
|
if (r < 0) {
|
|
rtems_printf(printer, "error: rtems-db: remote begin: %s: %s\n",
|
|
rtems_debugger->remote->name, strerror(errno));
|
|
free(rtems_debugger);
|
|
rtems_debugger = NULL;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Reset at the end of the session.
|
|
*/
|
|
rtems_debugger->flags = 0;
|
|
rtems_debugger->ack_pending = false;
|
|
|
|
r = rtems_debugger_lock_create();
|
|
if (r < 0) {
|
|
free(rtems_debugger);
|
|
rtems_debugger = NULL;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rtems_debugger_destroy(void)
|
|
{
|
|
int r;
|
|
int rr;
|
|
|
|
rtems_debugger_lock();
|
|
rtems_debugger->server_running = false;
|
|
rtems_debugger_unlock();
|
|
|
|
r = rtems_debugger_remote_disconnect();
|
|
|
|
rr = rtems_debugger->remote->end(rtems_debugger->remote);
|
|
if (rr < 0 && r == 0)
|
|
r = rr;
|
|
|
|
rr = rtems_debugger_task_destroy("DBSs",
|
|
rtems_debugger->server_task,
|
|
&rtems_debugger->server_finished,
|
|
RTEMS_DEBUGGER_TIMEOUT_STOP);
|
|
if (rr < 0 && r == 0)
|
|
r = rr;
|
|
|
|
rr = rtems_debugger_lock_destroy();
|
|
if (rr < 0 && r == 0)
|
|
r = rr;
|
|
|
|
free(rtems_debugger);
|
|
rtems_debugger = NULL;
|
|
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
rtems_debugger_main(rtems_task_argument arg)
|
|
{
|
|
int r;
|
|
|
|
rtems_debugger_lock();
|
|
rtems_debugger->server_running = true;
|
|
rtems_debugger->server_finished = false;
|
|
rtems_debugger_unlock();
|
|
|
|
rtems_debugger_printf("rtems-db: remote running\n");
|
|
|
|
while (rtems_debugger_server_running()) {
|
|
r = rtems_debugger_remote_connect();
|
|
if (r < 0)
|
|
break;
|
|
rtems_debugger_session();
|
|
rtems_debugger_remote_disconnect();
|
|
}
|
|
|
|
rtems_debugger_printf("rtems-db: remote finishing\n");
|
|
|
|
rtems_debugger_lock();
|
|
rtems_debugger->server_running = false;
|
|
rtems_debugger->server_finished = true;
|
|
rtems_debugger_unlock();
|
|
|
|
rtems_task_exit();
|
|
}
|
|
|
|
int
|
|
rtems_debugger_start(const char* remote,
|
|
const char* device,
|
|
int timeout,
|
|
rtems_task_priority priority,
|
|
const rtems_printer* printer)
|
|
{
|
|
int r;
|
|
|
|
r = rtems_debugger_create(remote, device, priority, timeout, printer);
|
|
if (r < 0)
|
|
return -1;
|
|
|
|
rtems_debugger_lock();
|
|
rtems_debugger->server_running = false;
|
|
rtems_debugger->server_finished = true;
|
|
_Condition_Initialize_named(&rtems_debugger->server_cond, "DBserver");
|
|
rtems_debugger_unlock();
|
|
|
|
r = rtems_debugger_task_create("DBSs",
|
|
priority,
|
|
RTEMS_DEBUGGER_STACKSIZE,
|
|
rtems_debugger_main,
|
|
0,
|
|
&rtems_debugger->server_task);
|
|
if (r < 0) {
|
|
rtems_debugger_destroy();
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
rtems_debugger_server_crash(void)
|
|
{
|
|
rtems_debugger_lock();
|
|
rtems_debugger->server_running = false;
|
|
rtems_debugger_unlock();
|
|
rtems_debugger->remote->end(rtems_debugger->remote);
|
|
}
|
|
|
|
int
|
|
rtems_debugger_break(bool wait)
|
|
{
|
|
int r = 0;
|
|
if (!rtems_debugger_running()) {
|
|
errno = EIO;
|
|
r = -1;
|
|
} else {
|
|
rtems_debugger_lock();
|
|
if (rtems_debugger_server_events_running()) {
|
|
rtems_debugger_server_events_signal();
|
|
} else if (
|
|
wait && !rtems_debugger_server_flag(RTEMS_DEBUGGER_FLAG_BREAK_WAITER)) {
|
|
rtems_debugger->flags |= RTEMS_DEBUGGER_FLAG_BREAK_WAITER;
|
|
rtems_debugger_server_events_wait();
|
|
} else {
|
|
errno = EIO;
|
|
r = -1;
|
|
}
|
|
rtems_debugger_unlock();
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int
|
|
rtems_debugger_stop(void)
|
|
{
|
|
return rtems_debugger_destroy();
|
|
}
|
|
|
|
bool
|
|
rtems_debugger_running(void)
|
|
{
|
|
return rtems_debugger != NULL;
|
|
}
|
|
|
|
void
|
|
rtems_debugger_set_verbose(bool on)
|
|
{
|
|
if (rtems_debugger_running()) {
|
|
if (on)
|
|
rtems_debugger->flags |= RTEMS_DEBUGGER_FLAG_VERBOSE;
|
|
else
|
|
rtems_debugger->flags &= ~RTEMS_DEBUGGER_FLAG_VERBOSE;
|
|
}
|
|
}
|
|
|
|
int
|
|
rtems_debugger_remote_debug(bool state)
|
|
{
|
|
if (rtems_debugger_running()) {
|
|
rtems_debugger_lock();
|
|
rtems_debugger->remote_debug = state;
|
|
rtems_debugger_printf("rtems-db: remote-debug is %s\n",
|
|
rtems_debugger->remote_debug ? "on" : "off");
|
|
rtems_debugger_unlock();
|
|
} else {
|
|
rtems_debugger_printf("rtems-db: debug server not running\n");
|
|
}
|
|
return 0;
|
|
}
|