#include "qemu/ctype.h"
#include "qemu/cutils.h"
#include "qemu/module.h"
-#include "trace-root.h"
+#include "trace/trace-root.h"
#ifdef CONFIG_USER_ONLY
#include "qemu.h"
#else
#include "sysemu/runstate.h"
#include "hw/semihosting/semihost.h"
#include "exec/exec-all.h"
+#include "sysemu/replay.h"
#ifdef CONFIG_USER_ONLY
#define GDB_ATTACHED "0"
int signal;
#ifdef CONFIG_USER_ONLY
int fd;
+ char *socket_path;
int running_state;
#else
CharBackend chr;
*/
static int sstep_flags = SSTEP_ENABLE|SSTEP_NOIRQ|SSTEP_NOTIMER;
+/* Retrieves flags for single step mode. */
+static int get_sstep_flags(void)
+{
+ /*
+ * In replay mode all events written into the log should be replayed.
+ * That is why NOIRQ flag is removed in this mode.
+ */
+ if (replay_mode != REPLAY_MODE_NONE) {
+ return SSTEP_ENABLE;
+ } else {
+ return sstep_flags;
+ }
+}
+
static GDBState gdbserver_state;
static void init_gdbserver_state(void)
bool gdb_has_xml;
#ifdef CONFIG_USER_ONLY
-/* XXX: This is not thread safe. Do we care? */
-static int gdbserver_fd = -1;
static int get_char(void)
{
break; /* nothing to do here */
case 's':
trace_gdbstub_op_stepping(cpu->cpu_index);
- cpu_single_step(cpu, sstep_flags);
+ cpu_single_step(cpu, get_sstep_flags());
cpu_resume(cpu);
flag = 1;
break;
cur_action = *p++;
if (cur_action == 'C' || cur_action == 'S') {
cur_action = qemu_tolower(cur_action);
- res = qemu_strtoul(p + 1, &p, 16, &tmp);
+ res = qemu_strtoul(p, &p, 16, &tmp);
if (res) {
goto out;
}
gdb_set_cpu_pc((target_ulong)gdb_ctx->params[0].val_ull);
}
- cpu_single_step(gdbserver_state.c_cpu, sstep_flags);
+ cpu_single_step(gdbserver_state.c_cpu, get_sstep_flags());
gdb_continue();
}
+static void handle_backward(GdbCmdContext *gdb_ctx, void *user_ctx)
+{
+ if (replay_mode != REPLAY_MODE_PLAY) {
+ put_packet("E22");
+ }
+ if (gdb_ctx->num_params == 1) {
+ switch (gdb_ctx->params[0].opcode) {
+ case 's':
+ if (replay_reverse_step()) {
+ gdb_continue();
+ } else {
+ put_packet("E14");
+ }
+ return;
+ case 'c':
+ if (replay_reverse_continue()) {
+ gdb_continue();
+ } else {
+ put_packet("E14");
+ }
+ return;
+ }
+ }
+
+ /* Default invalid command */
+ put_packet("");
+}
+
static void handle_v_cont_query(GdbCmdContext *gdb_ctx, void *user_ctx)
{
put_packet("vCont;c;C;s;S");
/* Kill the target */
put_packet("OK");
error_report("QEMU: Terminated via GDBstub");
+ gdb_exit(0);
exit(0);
}
/* Print the CPU model and name in multiprocess mode */
ObjectClass *oc = object_get_class(OBJECT(cpu));
const char *cpu_model = object_class_get_name(oc);
- g_autofree char *cpu_name;
- cpu_name = object_get_canonical_path_component(OBJECT(cpu));
+ const char *cpu_name =
+ object_get_canonical_path_component(OBJECT(cpu));
g_string_printf(rs, "%s %s [%s]", cpu_model, cpu_name,
cpu->halted ? "halted " : "running");
} else {
g_string_append(gdbserver_state.str_buf, ";qXfer:features:read+");
}
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ g_string_append(gdbserver_state.str_buf,
+ ";ReverseStep+;ReverseContinue+");
+ }
+
+#ifdef CONFIG_USER_ONLY
+ if (gdbserver_state.c_cpu->opaque) {
+ g_string_append(gdbserver_state.str_buf, ";qXfer:auxv:read+");
+ }
+#endif
+
if (gdb_ctx->num_params &&
strstr(gdb_ctx->params[0].data, "multiprocess+")) {
gdbserver_state.multiprocess = true;
gdbserver_state.str_buf->len, true);
}
+#if defined(CONFIG_USER_ONLY) && defined(CONFIG_LINUX_USER)
+static void handle_query_xfer_auxv(GdbCmdContext *gdb_ctx, void *user_ctx)
+{
+ TaskState *ts;
+ unsigned long offset, len, saved_auxv, auxv_len;
+
+ if (gdb_ctx->num_params < 2) {
+ put_packet("E22");
+ return;
+ }
+
+ offset = gdb_ctx->params[0].val_ul;
+ len = gdb_ctx->params[1].val_ul;
+ ts = gdbserver_state.c_cpu->opaque;
+ saved_auxv = ts->info->saved_auxv;
+ auxv_len = ts->info->auxv_len;
+
+ if (offset >= auxv_len) {
+ put_packet("E00");
+ return;
+ }
+
+ if (len > (MAX_PACKET_LENGTH - 5) / 2) {
+ len = (MAX_PACKET_LENGTH - 5) / 2;
+ }
+
+ if (len < auxv_len - offset) {
+ g_string_assign(gdbserver_state.str_buf, "m");
+ } else {
+ g_string_assign(gdbserver_state.str_buf, "l");
+ len = auxv_len - offset;
+ }
+
+ g_byte_array_set_size(gdbserver_state.mem_buf, len);
+ if (target_memory_rw_debug(gdbserver_state.g_cpu, saved_auxv + offset,
+ gdbserver_state.mem_buf->data, len, false)) {
+ put_packet("E14");
+ return;
+ }
+
+ memtox(gdbserver_state.str_buf,
+ (const char *)gdbserver_state.mem_buf->data, len);
+ put_packet_binary(gdbserver_state.str_buf->str,
+ gdbserver_state.str_buf->len, true);
+}
+#endif
+
static void handle_query_attached(GdbCmdContext *gdb_ctx, void *user_ctx)
{
put_packet(GDB_ATTACHED);
.cmd_startswith = 1,
.schema = "s:l,l0"
},
+#if defined(CONFIG_USER_ONLY) && defined(CONFIG_LINUX_USER)
+ {
+ .handler = handle_query_xfer_auxv,
+ .cmd = "Xfer:auxv:read::",
+ .cmd_startswith = 1,
+ .schema = "l,l0"
+ },
+#endif
{
.handler = handle_query_attached,
.cmd = "Attached:",
case 'k':
/* Kill the target */
error_report("QEMU: Terminated via GDBstub");
+ gdb_exit(0);
exit(0);
case 'D':
{
cmd_parser = &step_cmd_desc;
}
break;
+ case 'b':
+ {
+ static const GdbCmdParseEntry backward_cmd_desc = {
+ .handler = handle_backward,
+ .cmd = "b",
+ .cmd_startswith = 1,
+ .schema = "o0"
+ };
+ cmd_parser = &backward_cmd_desc;
+ }
+ break;
case 'F':
{
static const GdbCmdParseEntry file_io_cmd_desc = {
}
/* Tell the remote gdb that the process has exited. */
-void gdb_exit(CPUArchState *env, int code)
+void gdb_exit(int code)
{
char buf[4];
return;
}
#ifdef CONFIG_USER_ONLY
- if (gdbserver_fd < 0 || gdbserver_state.fd < 0) {
+ if (gdbserver_state.socket_path) {
+ unlink(gdbserver_state.socket_path);
+ }
+ if (gdbserver_state.fd < 0) {
return;
}
#endif
char buf[256];
int n;
- if (gdbserver_fd < 0 || gdbserver_state.fd < 0) {
+ if (!gdbserver_state.init || gdbserver_state.fd < 0) {
return sig;
}
{
char buf[4];
- if (gdbserver_fd < 0 || gdbserver_state.fd < 0) {
+ if (!gdbserver_state.init || gdbserver_state.fd < 0) {
return;
}
put_packet(buf);
}
-static bool gdb_accept(void)
+static void gdb_accept_init(int fd)
+{
+ init_gdbserver_state();
+ create_default_process(&gdbserver_state);
+ gdbserver_state.processes[0].attached = true;
+ gdbserver_state.c_cpu = gdb_first_attached_cpu();
+ gdbserver_state.g_cpu = gdbserver_state.c_cpu;
+ gdbserver_state.fd = fd;
+ gdb_has_xml = false;
+}
+
+static bool gdb_accept_socket(int gdb_fd)
+{
+ int fd;
+
+ for(;;) {
+ fd = accept(gdb_fd, NULL, NULL);
+ if (fd < 0 && errno != EINTR) {
+ perror("accept socket");
+ return false;
+ } else if (fd >= 0) {
+ qemu_set_cloexec(fd);
+ break;
+ }
+ }
+
+ gdb_accept_init(fd);
+ return true;
+}
+
+static int gdbserver_open_socket(const char *path)
+{
+ struct sockaddr_un sockaddr;
+ int fd, ret;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ perror("create socket");
+ return -1;
+ }
+
+ sockaddr.sun_family = AF_UNIX;
+ pstrcpy(sockaddr.sun_path, sizeof(sockaddr.sun_path) - 1, path);
+ ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
+ if (ret < 0) {
+ perror("bind socket");
+ close(fd);
+ return -1;
+ }
+ ret = listen(fd, 1);
+ if (ret < 0) {
+ perror("listen socket");
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static bool gdb_accept_tcp(int gdb_fd)
{
struct sockaddr_in sockaddr;
socklen_t len;
for(;;) {
len = sizeof(sockaddr);
- fd = accept(gdbserver_fd, (struct sockaddr *)&sockaddr, &len);
+ fd = accept(gdb_fd, (struct sockaddr *)&sockaddr, &len);
if (fd < 0 && errno != EINTR) {
perror("accept");
return false;
return false;
}
- init_gdbserver_state();
- create_default_process(&gdbserver_state);
- gdbserver_state.processes[0].attached = true;
- gdbserver_state.c_cpu = gdb_first_attached_cpu();
- gdbserver_state.g_cpu = gdbserver_state.c_cpu;
- gdbserver_state.fd = fd;
- gdb_has_xml = false;
+ gdb_accept_init(fd);
return true;
}
-static int gdbserver_open(int port)
+static int gdbserver_open_port(int port)
{
struct sockaddr_in sockaddr;
int fd, ret;
close(fd);
return -1;
}
+
return fd;
}
-int gdbserver_start(int port)
+int gdbserver_start(const char *port_or_path)
{
- gdbserver_fd = gdbserver_open(port);
- if (gdbserver_fd < 0)
- return -1;
- /* accept connections */
- if (!gdb_accept()) {
- close(gdbserver_fd);
- gdbserver_fd = -1;
+ int port = g_ascii_strtoull(port_or_path, NULL, 10);
+ int gdb_fd;
+
+ if (port > 0) {
+ gdb_fd = gdbserver_open_port(port);
+ } else {
+ gdb_fd = gdbserver_open_socket(port_or_path);
+ }
+
+ if (gdb_fd < 0) {
return -1;
}
- return 0;
+
+ if (port > 0 && gdb_accept_tcp(gdb_fd)) {
+ return 0;
+ } else if (gdb_accept_socket(gdb_fd)) {
+ gdbserver_state.socket_path = g_strdup(port_or_path);
+ return 0;
+ }
+
+ /* gone wrong */
+ close(gdb_fd);
+ return -1;
}
/* Disable gdb stub for child processes. */
void gdbserver_fork(CPUState *cpu)
{
- if (gdbserver_fd < 0 || gdbserver_state.fd < 0) {
+ if (!gdbserver_state.init || gdbserver_state.fd < 0) {
return;
}
close(gdbserver_state.fd);
s->g_cpu = s->c_cpu;
vm_stop(RUN_STATE_PAUSED);
+ replay_gdb_attached();
gdb_has_xml = false;
break;
default:
if (strstart(device, "tcp:", NULL)) {
/* enforce required TCP attributes */
snprintf(gdbstub_device_name, sizeof(gdbstub_device_name),
- "%s,nowait,nodelay,server", device);
+ "%s,wait=off,nodelay=on,server=on", device);
device = gdbstub_device_name;
}
#ifndef _WIN32
return 0;
}
-void gdbserver_cleanup(void)
-{
- if (gdbserver_state.init) {
- put_packet("W00");
- }
-}
-
static void register_types(void)
{
type_register_static(&char_gdb_type_info);