From 1ea96f1ded914c64c5a6df78b8d0663daebc828c Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 5 Mar 2024 12:09:38 +0000 Subject: gdbstub: Support disablement in a multi-threaded process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming follow-fork-mode child support will require disabling gdbstub in the parent process, which may have multiple threads (which are represented as CPUs). Loop over all CPUs in order to remove breakpoints and disable single-step. Move the respective code into a separate function. Reviewed-by: Alex Bennée Reviewed-by: Richard Henderson Signed-off-by: Ilya Leoshkevich Message-Id: <20240219141628.246823-2-iii@linux.ibm.com> Signed-off-by: Alex Bennée Message-Id: <20240305121005.3528075-3-alex.bennee@linaro.org> --- gdbstub/user.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'gdbstub/user.c') diff --git a/gdbstub/user.c b/gdbstub/user.c index 14918d1a21..3ce20b7bbf 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -356,16 +356,27 @@ int gdbserver_start(const char *port_or_path) return -1; } +static void disable_gdbstub(CPUState *thread_cpu) +{ + CPUState *cpu; + + close(gdbserver_user_state.fd); + gdbserver_user_state.fd = -1; + CPU_FOREACH(cpu) { + cpu_breakpoint_remove_all(cpu, BP_GDB); + /* no cpu_watchpoint_remove_all for user-mode */ + cpu_single_step(cpu, 0); + } + tb_flush(thread_cpu); +} + /* Disable gdb stub for child processes. */ void gdbserver_fork(CPUState *cpu) { if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { return; } - close(gdbserver_user_state.fd); - gdbserver_user_state.fd = -1; - cpu_breakpoint_remove_all(cpu, BP_GDB); - /* no cpu_watchpoint_remove_all for user-mode */ + disable_gdbstub(cpu); } /* -- cgit 1.4.1 From 3d6ed98da82e0bb2384bfbc83fadb518271fdbca Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 5 Mar 2024 12:09:41 +0000 Subject: gdbstub: Introduce gdbserver_fork_start() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming follow-fork-mode child support requires knowing when fork() is about to happen in order to initialize its state. Add a hook for that. Reviewed-by: Alex Bennée Signed-off-by: Ilya Leoshkevich Message-Id: <20240219141628.246823-5-iii@linux.ibm.com> Signed-off-by: Alex Bennée Message-Id: <20240305121005.3528075-6-alex.bennee@linaro.org> --- bsd-user/main.c | 1 + gdbstub/user.c | 4 ++++ include/gdbstub/user.h | 5 +++++ linux-user/main.c | 1 + 4 files changed, 11 insertions(+) (limited to 'gdbstub/user.c') diff --git a/bsd-user/main.c b/bsd-user/main.c index e39eef3040..517c6b3ec2 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -113,6 +113,7 @@ void fork_start(void) start_exclusive(); cpu_list_lock(); mmap_fork_start(); + gdbserver_fork_start(); } void fork_end(int child) diff --git a/gdbstub/user.c b/gdbstub/user.c index 3ce20b7bbf..536fb43b03 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -356,6 +356,10 @@ int gdbserver_start(const char *port_or_path) return -1; } +void gdbserver_fork_start(void) +{ +} + static void disable_gdbstub(CPUState *thread_cpu) { CPUState *cpu; diff --git a/include/gdbstub/user.h b/include/gdbstub/user.h index 68b6534130..e33f8d9a9a 100644 --- a/include/gdbstub/user.h +++ b/include/gdbstub/user.h @@ -45,6 +45,11 @@ static inline int gdb_handlesig(CPUState *cpu, int sig) */ void gdb_signalled(CPUArchState *as, int sig); +/** + * gdbserver_fork_start() - inform gdb of the upcoming fork() + */ +void gdbserver_fork_start(void); + /** * gdbserver_fork() - disable gdb stub for child processes. * @cs: CPU diff --git a/linux-user/main.c b/linux-user/main.c index 699da77371..755c566d6d 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -145,6 +145,7 @@ void fork_start(void) mmap_fork_start(); cpu_list_lock(); qemu_plugin_user_prefork_lock(); + gdbserver_fork_start(); } void fork_end(int child) -- cgit 1.4.1 From 9d456e092da670324f37f269d29300eedb915c3b Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 5 Mar 2024 12:09:43 +0000 Subject: {linux,bsd}-user: Pass pid to gdbserver_fork() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming follow-fork-mode child support requires knowing the child pid. Pass it down. Reviewed-by: Richard Henderson Signed-off-by: Ilya Leoshkevich Message-Id: <20240219141628.246823-7-iii@linux.ibm.com> Signed-off-by: Alex Bennée Message-Id: <20240305121005.3528075-8-alex.bennee@linaro.org> --- bsd-user/main.c | 2 +- gdbstub/user.c | 2 +- include/gdbstub/user.h | 2 +- linux-user/main.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'gdbstub/user.c') diff --git a/bsd-user/main.c b/bsd-user/main.c index fca9b30204..0dbd1cf880 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -138,7 +138,7 @@ void fork_end(pid_t pid) */ qemu_init_cpu_list(); get_task_state(thread_cpu)->ts_tid = qemu_get_thread_id(); - gdbserver_fork(thread_cpu); + gdbserver_fork(thread_cpu, pid); } else { mmap_fork_end(child); cpu_list_unlock(); diff --git a/gdbstub/user.c b/gdbstub/user.c index 536fb43b03..c61e1a0d1f 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -375,7 +375,7 @@ static void disable_gdbstub(CPUState *thread_cpu) } /* Disable gdb stub for child processes. */ -void gdbserver_fork(CPUState *cpu) +void gdbserver_fork(CPUState *cpu, pid_t pid) { if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { return; diff --git a/include/gdbstub/user.h b/include/gdbstub/user.h index e33f8d9a9a..3f9f45946e 100644 --- a/include/gdbstub/user.h +++ b/include/gdbstub/user.h @@ -54,7 +54,7 @@ void gdbserver_fork_start(void); * gdbserver_fork() - disable gdb stub for child processes. * @cs: CPU */ -void gdbserver_fork(CPUState *cs); +void gdbserver_fork(CPUState *cs, pid_t pid); /** * gdb_syscall_entry() - inform gdb of syscall entry and yield control to it diff --git a/linux-user/main.c b/linux-user/main.c index cab95f5b0a..70314e0ab6 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -165,7 +165,7 @@ void fork_end(pid_t pid) } qemu_init_cpu_list(); get_task_state(thread_cpu)->ts_tid = qemu_get_thread_id(); - gdbserver_fork(thread_cpu); + gdbserver_fork(thread_cpu, pid); } else { cpu_list_unlock(); } -- cgit 1.4.1 From 6604b05763515f6329bf508ff1284651ebffc33e Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 5 Mar 2024 12:09:44 +0000 Subject: gdbstub: Call gdbserver_fork() both in parent and in child MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming follow-fork-mode child support requires post-fork message exchange between the parent and the child. Prepare gdbserver_fork() for this purpose. Rename it to gdbserver_fork_end() to better reflect its purpose. Reviewed-by: Richard Henderson Signed-off-by: Ilya Leoshkevich Message-Id: <20240219141628.246823-8-iii@linux.ibm.com> Signed-off-by: Alex Bennée Message-Id: <20240305121005.3528075-9-alex.bennee@linaro.org> --- bsd-user/main.c | 3 ++- gdbstub/user.c | 5 ++--- include/gdbstub/user.h | 5 +++-- linux-user/main.c | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) (limited to 'gdbstub/user.c') diff --git a/bsd-user/main.c b/bsd-user/main.c index 0dbd1cf880..3dc285e5b7 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -138,10 +138,11 @@ void fork_end(pid_t pid) */ qemu_init_cpu_list(); get_task_state(thread_cpu)->ts_tid = qemu_get_thread_id(); - gdbserver_fork(thread_cpu, pid); + gdbserver_fork_end(thread_cpu, pid); } else { mmap_fork_end(child); cpu_list_unlock(); + gdbserver_fork_end(thread_cpu, pid); end_exclusive(); } } diff --git a/gdbstub/user.c b/gdbstub/user.c index c61e1a0d1f..866a25f9c0 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -374,10 +374,9 @@ static void disable_gdbstub(CPUState *thread_cpu) tb_flush(thread_cpu); } -/* Disable gdb stub for child processes. */ -void gdbserver_fork(CPUState *cpu, pid_t pid) +void gdbserver_fork_end(CPUState *cpu, pid_t pid) { - if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { + if (pid != 0 || !gdbserver_state.init || gdbserver_user_state.fd < 0) { return; } disable_gdbstub(cpu); diff --git a/include/gdbstub/user.h b/include/gdbstub/user.h index 3f9f45946e..4c4e5c4c58 100644 --- a/include/gdbstub/user.h +++ b/include/gdbstub/user.h @@ -51,10 +51,11 @@ void gdb_signalled(CPUArchState *as, int sig); void gdbserver_fork_start(void); /** - * gdbserver_fork() - disable gdb stub for child processes. + * gdbserver_fork_end() - inform gdb of the completed fork() * @cs: CPU + * @pid: 0 if in child process, -1 if fork failed, child process pid otherwise */ -void gdbserver_fork(CPUState *cs, pid_t pid); +void gdbserver_fork_end(CPUState *cs, pid_t pid); /** * gdb_syscall_entry() - inform gdb of syscall entry and yield control to it diff --git a/linux-user/main.c b/linux-user/main.c index 70314e0ab6..41caa77cb5 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -165,10 +165,10 @@ void fork_end(pid_t pid) } qemu_init_cpu_list(); get_task_state(thread_cpu)->ts_tid = qemu_get_thread_id(); - gdbserver_fork(thread_cpu, pid); } else { cpu_list_unlock(); } + gdbserver_fork_end(thread_cpu, pid); /* * qemu_init_cpu_list() reinitialized the child exclusive state, but we * also need to keep current_cpu consistent, so call end_exclusive() for -- cgit 1.4.1 From 6d923112fd18f73bd55cbae23ae5a8023457b85c Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 5 Mar 2024 12:09:45 +0000 Subject: gdbstub: Introduce gdb_handle_query_supported_user() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming follow-fork-mode child support requires advertising the fork-events feature, which is user-specific. Introduce a user-specific hook for this. Reviewed-by: Richard Henderson Signed-off-by: Ilya Leoshkevich Message-Id: <20240219141628.246823-9-iii@linux.ibm.com> Signed-off-by: Alex Bennée Message-Id: <20240305121005.3528075-10-alex.bennee@linaro.org> --- gdbstub/gdbstub.c | 12 +++++++++--- gdbstub/internals.h | 1 + gdbstub/user.c | 4 ++++ 3 files changed, 14 insertions(+), 3 deletions(-) (limited to 'gdbstub/user.c') diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 2909bc8c69..7be4418dcb 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -1655,9 +1655,15 @@ static void handle_query_supported(GArray *params, void *user_ctx) g_string_append(gdbserver_state.str_buf, ";qXfer:exec-file:read+"); #endif - if (params->len && - strstr(get_param(params, 0)->data, "multiprocess+")) { - gdbserver_state.multiprocess = true; + if (params->len) { + const char *gdb_supported = get_param(params, 0)->data; + + if (strstr(gdb_supported, "multiprocess+")) { + gdbserver_state.multiprocess = true; + } +#if defined(CONFIG_USER_ONLY) + gdb_handle_query_supported_user(gdb_supported); +#endif } g_string_append(gdbserver_state.str_buf, ";vContSupported+;multiprocess+"); diff --git a/gdbstub/internals.h b/gdbstub/internals.h index 56b7c13b75..e6063835b1 100644 --- a/gdbstub/internals.h +++ b/gdbstub/internals.h @@ -196,6 +196,7 @@ void gdb_handle_v_file_pread(GArray *params, void *user_ctx); /* user */ void gdb_handle_v_file_readlink(GArray *params, void *user_ctx); /* user */ void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx); /* user */ void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx); /* user */ +void gdb_handle_query_supported_user(const char *gdb_supported); /* user */ void gdb_handle_query_attached(GArray *params, void *user_ctx); /* both */ diff --git a/gdbstub/user.c b/gdbstub/user.c index 866a25f9c0..c9e8b83d72 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -382,6 +382,10 @@ void gdbserver_fork_end(CPUState *cpu, pid_t pid) disable_gdbstub(cpu); } +void gdb_handle_query_supported_user(const char *gdb_supported) +{ +} + /* * Execution state helpers */ -- cgit 1.4.1 From e454f2fe24793afd1fa596deb42b714478adf73b Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 5 Mar 2024 12:09:46 +0000 Subject: gdbstub: Introduce gdb_handle_set_thread_user() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming follow-fork-mode child support needs to perform certain actions when GDB switches between the stopped parent and the stopped child. Introduce a user-specific hook for this. Signed-off-by: Ilya Leoshkevich Message-Id: <20240219141628.246823-10-iii@linux.ibm.com> Signed-off-by: Alex Bennée Message-Id: <20240305121005.3528075-11-alex.bennee@linaro.org> --- gdbstub/gdbstub.c | 11 +++++++++-- gdbstub/internals.h | 1 + gdbstub/user.c | 5 +++++ 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'gdbstub/user.c') diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 7be4418dcb..3eb93162aa 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -1099,6 +1099,7 @@ static void handle_cont_with_sig(GArray *params, void *user_ctx) static void handle_set_thread(GArray *params, void *user_ctx) { + uint32_t pid, tid; CPUState *cpu; if (params->len != 2) { @@ -1116,8 +1117,14 @@ static void handle_set_thread(GArray *params, void *user_ctx) return; } - cpu = gdb_get_cpu(get_param(params, 1)->thread_id.pid, - get_param(params, 1)->thread_id.tid); + pid = get_param(params, 1)->thread_id.pid; + tid = get_param(params, 1)->thread_id.tid; +#ifdef CONFIG_USER_ONLY + if (gdb_handle_set_thread_user(pid, tid)) { + return; + } +#endif + cpu = gdb_get_cpu(pid, tid); if (!cpu) { gdb_put_packet("E22"); return; diff --git a/gdbstub/internals.h b/gdbstub/internals.h index e6063835b1..b4905c7181 100644 --- a/gdbstub/internals.h +++ b/gdbstub/internals.h @@ -197,6 +197,7 @@ void gdb_handle_v_file_readlink(GArray *params, void *user_ctx); /* user */ void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx); /* user */ void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx); /* user */ void gdb_handle_query_supported_user(const char *gdb_supported); /* user */ +bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid); /* user */ void gdb_handle_query_attached(GArray *params, void *user_ctx); /* both */ diff --git a/gdbstub/user.c b/gdbstub/user.c index c9e8b83d72..b048754c4f 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -386,6 +386,11 @@ void gdb_handle_query_supported_user(const char *gdb_supported) { } +bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid) +{ + return false; +} + /* * Execution state helpers */ -- cgit 1.4.1 From 539cb4ec49ec96aeb9dddba235c2f8bafc1c4fab Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 5 Mar 2024 12:09:47 +0000 Subject: gdbstub: Introduce gdb_handle_detach_user() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming follow-fork-mode child support needs to perform certain actions when GDB detaches from the stopped parent or the stopped child. Introduce a user-specific hook for this. Signed-off-by: Ilya Leoshkevich Message-Id: <20240219141628.246823-11-iii@linux.ibm.com> Signed-off-by: Alex Bennée Message-Id: <20240305121005.3528075-12-alex.bennee@linaro.org> --- gdbstub/gdbstub.c | 6 ++++++ gdbstub/internals.h | 1 + gdbstub/user.c | 5 +++++ 3 files changed, 12 insertions(+) (limited to 'gdbstub/user.c') diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 3eb93162aa..17efcae0d0 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -1024,6 +1024,12 @@ static void handle_detach(GArray *params, void *user_ctx) pid = get_param(params, 0)->val_ul; } +#ifdef CONFIG_USER_ONLY + if (gdb_handle_detach_user(pid)) { + return; + } +#endif + process = gdb_get_process(pid); gdb_process_breakpoint_remove_all(process); process->attached = false; diff --git a/gdbstub/internals.h b/gdbstub/internals.h index b4905c7181..b472459838 100644 --- a/gdbstub/internals.h +++ b/gdbstub/internals.h @@ -198,6 +198,7 @@ void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx); /* user */ void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx); /* user */ void gdb_handle_query_supported_user(const char *gdb_supported); /* user */ bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid); /* user */ +bool gdb_handle_detach_user(uint32_t pid); /* user */ void gdb_handle_query_attached(GArray *params, void *user_ctx); /* both */ diff --git a/gdbstub/user.c b/gdbstub/user.c index b048754c4f..1a7b582a40 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -391,6 +391,11 @@ bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid) return false; } +bool gdb_handle_detach_user(uint32_t pid) +{ + return false; +} + /* * Execution state helpers */ -- cgit 1.4.1 From d547e711a8a520b1a160958443c0851a6767f95b Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Tue, 5 Mar 2024 12:09:48 +0000 Subject: gdbstub: Implement follow-fork-mode child MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently it's not possible to use gdbstub for debugging linux-user code that runs in a forked child, which is normally done using the `set follow-fork-mode child` GDB command. Purely on the protocol level, the missing piece is the fork-events feature. However, a deeper problem is supporting $Hg switching between different processes - right now it can do only threads. Implementing this for the general case would be quite complicated, but, fortunately, for the follow-fork-mode case there are a few factors that greatly simplify things: fork() happens in the exclusive section, there are only two processes involved, and before one of them is resumed, the second one is detached. This makes it possible to implement a simplified scheme: the parent and the child share the gdbserver socket, it's used only by one of them at any given time, which is coordinated through a separate socketpair. The processes can read from the gdbserver socket only one byte at a time, which is not great for performance, but, fortunately, the follow-fork-mode handling involves only a few messages. Advertise the fork-events support, and remember whether GDB has it as well. Implement the state machine that is initialized on fork(), decides the current owner of the gdbserver socket, and is terminated when one of the two processes is detached. The logic for the parent and the child is the same, only the initial state is different. Signed-off-by: Ilya Leoshkevich Message-Id: <20240219141628.246823-12-iii@linux.ibm.com> Signed-off-by: Alex Bennée Message-Id: <20240305121005.3528075-13-alex.bennee@linaro.org> --- gdbstub/user.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 2 deletions(-) (limited to 'gdbstub/user.c') diff --git a/gdbstub/user.c b/gdbstub/user.c index 1a7b582a40..7f9f19a124 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -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; } -- cgit 1.4.1