|
@@ -25,6 +25,61 @@
|
|
|
#define GDB_NR_SYSCALLS 1024
|
|
|
typedef unsigned long GDBSyscallsMask[BITS_TO_LONGS(GDB_NR_SYSCALLS)];
|
|
|
|
|
|
+/*
|
|
|
+ * Forked child talks to its parent in order to let GDB enforce the
|
|
|
+ * follow-fork-mode. This happens inside a start_exclusive() section, so that
|
|
|
+ * the other threads, which may be forking too, do not interfere. The
|
|
|
+ * implementation relies on GDB not sending $vCont until it has detached
|
|
|
+ * either from the parent (follow-fork-mode child) or from the child
|
|
|
+ * (follow-fork-mode parent).
|
|
|
+ *
|
|
|
+ * The parent and the child share the GDB socket; at any given time only one
|
|
|
+ * of them is allowed to use it, as is reflected in the respective fork_state.
|
|
|
+ * This is negotiated via the fork_sockets pair as a reaction to $Hg.
|
|
|
+ *
|
|
|
+ * Below is a short summary of the possible state transitions:
|
|
|
+ *
|
|
|
+ * ENABLED : Terminal state.
|
|
|
+ * DISABLED : Terminal state.
|
|
|
+ * ACTIVE : Parent initial state.
|
|
|
+ * INACTIVE : Child initial state.
|
|
|
+ * ACTIVE -> DEACTIVATING: On $Hg.
|
|
|
+ * ACTIVE -> ENABLING : On $D.
|
|
|
+ * ACTIVE -> DISABLING : On $D.
|
|
|
+ * ACTIVE -> DISABLED : On communication error.
|
|
|
+ * DEACTIVATING -> INACTIVE : On gdb_read_byte() return.
|
|
|
+ * DEACTIVATING -> DISABLED : On communication error.
|
|
|
+ * INACTIVE -> ACTIVE : On $Hg in the peer.
|
|
|
+ * INACTIVE -> ENABLE : On $D in the peer.
|
|
|
+ * INACTIVE -> DISABLE : On $D in the peer.
|
|
|
+ * INACTIVE -> DISABLED : On communication error.
|
|
|
+ * ENABLING -> ENABLED : On gdb_read_byte() return.
|
|
|
+ * ENABLING -> DISABLED : On communication error.
|
|
|
+ * DISABLING -> DISABLED : On gdb_read_byte() return.
|
|
|
+ */
|
|
|
+enum GDBForkState {
|
|
|
+ /* Fully owning the GDB socket. */
|
|
|
+ GDB_FORK_ENABLED,
|
|
|
+ /* Working with the GDB socket; the peer is inactive. */
|
|
|
+ GDB_FORK_ACTIVE,
|
|
|
+ /* Handing off the GDB socket to the peer. */
|
|
|
+ GDB_FORK_DEACTIVATING,
|
|
|
+ /* The peer is working with the GDB socket. */
|
|
|
+ GDB_FORK_INACTIVE,
|
|
|
+ /* Asking the peer to close its GDB socket fd. */
|
|
|
+ GDB_FORK_ENABLING,
|
|
|
+ /* Asking the peer to take over, closing our GDB socket fd. */
|
|
|
+ GDB_FORK_DISABLING,
|
|
|
+ /* The peer has taken over, our GDB socket fd is closed. */
|
|
|
+ GDB_FORK_DISABLED,
|
|
|
+};
|
|
|
+
|
|
|
+enum GDBForkMessage {
|
|
|
+ GDB_FORK_ACTIVATE = 'a',
|
|
|
+ GDB_FORK_ENABLE = 'e',
|
|
|
+ GDB_FORK_DISABLE = 'd',
|
|
|
+};
|
|
|
+
|
|
|
/* User-mode specific state */
|
|
|
typedef struct {
|
|
|
int fd;
|
|
@@ -36,6 +91,10 @@ typedef struct {
|
|
|
*/
|
|
|
bool catch_all_syscalls;
|
|
|
GDBSyscallsMask catch_syscalls_mask;
|
|
|
+ bool fork_events;
|
|
|
+ enum GDBForkState fork_state;
|
|
|
+ int fork_sockets[2];
|
|
|
+ pid_t fork_peer_pid, fork_peer_tid;
|
|
|
} GDBUserState;
|
|
|
|
|
|
static GDBUserState gdbserver_user_state;
|
|
@@ -358,6 +417,18 @@ int gdbserver_start(const char *port_or_path)
|
|
|
|
|
|
void gdbserver_fork_start(void)
|
|
|
{
|
|
|
+ if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!gdbserver_user_state.fork_events ||
|
|
|
+ qemu_socketpair(AF_UNIX, SOCK_STREAM, 0,
|
|
|
+ gdbserver_user_state.fork_sockets) < 0) {
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_DISABLED;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_INACTIVE;
|
|
|
+ gdbserver_user_state.fork_peer_pid = getpid();
|
|
|
+ gdbserver_user_state.fork_peer_tid = qemu_get_thread_id();
|
|
|
}
|
|
|
|
|
|
static void disable_gdbstub(CPUState *thread_cpu)
|
|
@@ -376,23 +447,160 @@ static void disable_gdbstub(CPUState *thread_cpu)
|
|
|
|
|
|
void gdbserver_fork_end(CPUState *cpu, pid_t pid)
|
|
|
{
|
|
|
- if (pid != 0 || !gdbserver_state.init || gdbserver_user_state.fd < 0) {
|
|
|
+ char b;
|
|
|
+ int fd;
|
|
|
+
|
|
|
+ if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
|
|
|
return;
|
|
|
}
|
|
|
- disable_gdbstub(cpu);
|
|
|
+
|
|
|
+ if (pid == -1) {
|
|
|
+ if (gdbserver_user_state.fork_state != GDB_FORK_DISABLED) {
|
|
|
+ g_assert(gdbserver_user_state.fork_state == GDB_FORK_INACTIVE);
|
|
|
+ close(gdbserver_user_state.fork_sockets[0]);
|
|
|
+ close(gdbserver_user_state.fork_sockets[1]);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (gdbserver_user_state.fork_state == GDB_FORK_DISABLED) {
|
|
|
+ if (pid == 0) {
|
|
|
+ disable_gdbstub(cpu);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pid == 0) {
|
|
|
+ close(gdbserver_user_state.fork_sockets[0]);
|
|
|
+ fd = gdbserver_user_state.fork_sockets[1];
|
|
|
+ g_assert(gdbserver_state.process_num == 1);
|
|
|
+ g_assert(gdbserver_state.processes[0].pid ==
|
|
|
+ gdbserver_user_state.fork_peer_pid);
|
|
|
+ g_assert(gdbserver_state.processes[0].attached);
|
|
|
+ gdbserver_state.processes[0].pid = getpid();
|
|
|
+ } else {
|
|
|
+ close(gdbserver_user_state.fork_sockets[1]);
|
|
|
+ fd = gdbserver_user_state.fork_sockets[0];
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_ACTIVE;
|
|
|
+ gdbserver_user_state.fork_peer_pid = pid;
|
|
|
+ gdbserver_user_state.fork_peer_tid = pid;
|
|
|
+
|
|
|
+ if (!gdbserver_state.allow_stop_reply) {
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ g_string_printf(gdbserver_state.str_buf,
|
|
|
+ "T%02xfork:p%02x.%02x;thread:p%02x.%02x;",
|
|
|
+ gdb_target_signal_to_gdb(gdb_target_sigtrap()),
|
|
|
+ pid, pid, (int)getpid(), qemu_get_thread_id());
|
|
|
+ gdb_put_strbuf();
|
|
|
+ }
|
|
|
+
|
|
|
+ gdbserver_state.state = RS_IDLE;
|
|
|
+ gdbserver_state.allow_stop_reply = false;
|
|
|
+ gdbserver_user_state.running_state = 0;
|
|
|
+ for (;;) {
|
|
|
+ switch (gdbserver_user_state.fork_state) {
|
|
|
+ case GDB_FORK_ENABLED:
|
|
|
+ if (gdbserver_user_state.running_state) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ QEMU_FALLTHROUGH;
|
|
|
+ case GDB_FORK_ACTIVE:
|
|
|
+ if (read(gdbserver_user_state.fd, &b, 1) != 1) {
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ gdb_read_byte(b);
|
|
|
+ break;
|
|
|
+ case GDB_FORK_DEACTIVATING:
|
|
|
+ b = GDB_FORK_ACTIVATE;
|
|
|
+ if (write(fd, &b, 1) != 1) {
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_INACTIVE;
|
|
|
+ break;
|
|
|
+ case GDB_FORK_INACTIVE:
|
|
|
+ if (read(fd, &b, 1) != 1) {
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ switch (b) {
|
|
|
+ case GDB_FORK_ACTIVATE:
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_ACTIVE;
|
|
|
+ break;
|
|
|
+ case GDB_FORK_ENABLE:
|
|
|
+ close(fd);
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_ENABLED;
|
|
|
+ break;
|
|
|
+ case GDB_FORK_DISABLE:
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_DISABLED;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ g_assert_not_reached();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case GDB_FORK_ENABLING:
|
|
|
+ b = GDB_FORK_DISABLE;
|
|
|
+ if (write(fd, &b, 1) != 1) {
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ close(fd);
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_ENABLED;
|
|
|
+ break;
|
|
|
+ case GDB_FORK_DISABLING:
|
|
|
+ b = GDB_FORK_ENABLE;
|
|
|
+ if (write(fd, &b, 1) != 1) {
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_DISABLED;
|
|
|
+ break;
|
|
|
+ case GDB_FORK_DISABLED:
|
|
|
+ close(fd);
|
|
|
+ disable_gdbstub(cpu);
|
|
|
+ return;
|
|
|
+ default:
|
|
|
+ g_assert_not_reached();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+fail:
|
|
|
+ close(fd);
|
|
|
+ if (pid == 0) {
|
|
|
+ disable_gdbstub(cpu);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void gdb_handle_query_supported_user(const char *gdb_supported)
|
|
|
{
|
|
|
+ if (strstr(gdb_supported, "fork-events+")) {
|
|
|
+ gdbserver_user_state.fork_events = true;
|
|
|
+ }
|
|
|
+ g_string_append(gdbserver_state.str_buf, ";fork-events+");
|
|
|
}
|
|
|
|
|
|
bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid)
|
|
|
{
|
|
|
+ if (gdbserver_user_state.fork_state == GDB_FORK_ACTIVE &&
|
|
|
+ pid == gdbserver_user_state.fork_peer_pid &&
|
|
|
+ tid == gdbserver_user_state.fork_peer_tid) {
|
|
|
+ gdbserver_user_state.fork_state = GDB_FORK_DEACTIVATING;
|
|
|
+ gdb_put_packet("OK");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
bool gdb_handle_detach_user(uint32_t pid)
|
|
|
{
|
|
|
+ bool enable;
|
|
|
+
|
|
|
+ if (gdbserver_user_state.fork_state == GDB_FORK_ACTIVE) {
|
|
|
+ enable = pid == gdbserver_user_state.fork_peer_pid;
|
|
|
+ if (enable || pid == getpid()) {
|
|
|
+ gdbserver_user_state.fork_state = enable ? GDB_FORK_ENABLING :
|
|
|
+ GDB_FORK_DISABLING;
|
|
|
+ gdb_put_packet("OK");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
return false;
|
|
|
}
|
|
|
|