* [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child
@ 2024-02-05 11:22 Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 01/11] gdbstub: Support disablement in a multi-threaded process Ilya Leoshkevich
` (10 more replies)
0 siblings, 11 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
Based-on: <20240202152506.279476-1-iii@linux.ibm.com>
("[PATCH v3 0/5] gdbstub: Implement catching syscalls")
v1: https://lists.gnu.org/archive/html/qemu-devel/2024-01/msg06646.html
v1 -> v2: Factor out a number of prep patches;
Add a state transition diagram comment (Alex).
Improve a few comments;
Extend the ts_tid fix to bsd.
Hi,
I needed to debug a linux-user crash between fork() and exec() [1] and
realized that gdbstub does not allow this. This series lifts this
restriction (one still cannot debug past exec() though). Patches 1-9
are preliminary refactorings, patch 10 is the implementation, and patch
11 is the test.
[1] https://lists.gnu.org/archive/html/qemu-devel/2024-01/msg06424.html
Best regards,
Ilya
Ilya Leoshkevich (11):
gdbstub: Support disablement in a multi-threaded process
{linux,bsd}-user: Update ts_tid after fork()
gdbstub: Introduce gdbserver_fork_start()
{linux,bsd}-user: Pass pid to fork_end()
{linux,bsd}-user: Pass pid to gdbserver_fork()
gdbstub: Call gdbserver_fork() both in parent and in child
gdbstub: Introduce gdb_handle_query_supported_user()
gdbstub: Introduce gdb_handle_set_thread_user()
gdbstub: Introduce gdb_handle_detach_user()
gdbstub: Implement follow-fork-mode child
tests/tcg: Add two follow-fork-mode tests
bsd-user/freebsd/os-proc.h | 6 +-
bsd-user/main.c | 9 +-
bsd-user/qemu.h | 2 +-
gdbstub/gdbstub.c | 29 ++-
gdbstub/internals.h | 3 +
gdbstub/user.c | 244 +++++++++++++++++-
include/gdbstub/user.h | 11 +-
linux-user/main.c | 8 +-
linux-user/syscall.c | 6 +-
linux-user/user-internals.h | 2 +-
tests/tcg/multiarch/Makefile.target | 17 +-
tests/tcg/multiarch/follow-fork-mode.c | 56 ++++
.../gdbstub/follow-fork-mode-child.py | 40 +++
.../gdbstub/follow-fork-mode-parent.py | 16 ++
14 files changed, 424 insertions(+), 25 deletions(-)
create mode 100644 tests/tcg/multiarch/follow-fork-mode.c
create mode 100644 tests/tcg/multiarch/gdbstub/follow-fork-mode-child.py
create mode 100644 tests/tcg/multiarch/gdbstub/follow-fork-mode-parent.py
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 01/11] gdbstub: Support disablement in a multi-threaded process
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 02/11] {linux,bsd}-user: Update ts_tid after fork() Ilya Leoshkevich
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
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 watchpoints and disable
single-step. Move the respective code into a separate function.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
gdbstub/user.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/gdbstub/user.c b/gdbstub/user.c
index 8f3affbad47..e6809da2243 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(void)
+{
+ 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(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();
}
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 02/11] {linux,bsd}-user: Update ts_tid after fork()
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 01/11] gdbstub: Support disablement in a multi-threaded process Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 03/11] gdbstub: Introduce gdbserver_fork_start() Ilya Leoshkevich
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
Currently ts_tid contains the parent tid after fork(), which is not
correct. So far it has not affected anything, but the upcoming
follow-fork-mode child support relies on the correct value, so fix it.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
bsd-user/main.c | 1 +
linux-user/main.c | 1 +
2 files changed, 2 insertions(+)
diff --git a/bsd-user/main.c b/bsd-user/main.c
index e5efb7b8458..4140edc8311 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -127,6 +127,7 @@ void fork_end(int child)
* state, so we don't need to end_exclusive() here.
*/
qemu_init_cpu_list();
+ ((TaskState *)thread_cpu->opaque)->ts_tid = qemu_get_thread_id();
gdbserver_fork(thread_cpu);
} else {
mmap_fork_end(child);
diff --git a/linux-user/main.c b/linux-user/main.c
index 74b2fbb3938..e6427d72332 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -160,6 +160,7 @@ void fork_end(int child)
}
}
qemu_init_cpu_list();
+ ((TaskState *)thread_cpu->opaque)->ts_tid = qemu_get_thread_id();
gdbserver_fork(thread_cpu);
} else {
cpu_list_unlock();
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 03/11] gdbstub: Introduce gdbserver_fork_start()
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 01/11] gdbstub: Support disablement in a multi-threaded process Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 02/11] {linux,bsd}-user: Update ts_tid after fork() Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 04/11] {linux,bsd}-user: Pass pid to fork_end() Ilya Leoshkevich
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
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.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
bsd-user/main.c | 1 +
gdbstub/user.c | 4 ++++
include/gdbstub/user.h | 5 +++++
linux-user/main.c | 1 +
4 files changed, 11 insertions(+)
diff --git a/bsd-user/main.c b/bsd-user/main.c
index 4140edc8311..bfe6888ea89 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -106,6 +106,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 e6809da2243..85fd44b6aa9 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(void)
{
CPUState *cpu;
diff --git a/include/gdbstub/user.h b/include/gdbstub/user.h
index 68b6534130c..e33f8d9a9a6 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 e6427d72332..8c7bea1c631 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -144,6 +144,7 @@ void fork_start(void)
mmap_fork_start();
cpu_list_lock();
qemu_plugin_user_prefork_lock();
+ gdbserver_fork_start();
}
void fork_end(int child)
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 04/11] {linux,bsd}-user: Pass pid to fork_end()
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
` (2 preceding siblings ...)
2024-02-05 11:22 ` [PATCH v2 03/11] gdbstub: Introduce gdbserver_fork_start() Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 05/11] {linux,bsd}-user: Pass pid to gdbserver_fork() Ilya Leoshkevich
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
The upcoming follow-fork-mode child support requires knowing the child
pid. Pass it down.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
bsd-user/freebsd/os-proc.h | 6 +++---
bsd-user/main.c | 4 +++-
bsd-user/qemu.h | 2 +-
linux-user/main.c | 4 +++-
linux-user/syscall.c | 6 +++---
linux-user/user-internals.h | 2 +-
6 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/bsd-user/freebsd/os-proc.h b/bsd-user/freebsd/os-proc.h
index d6418780344..3003c8cb637 100644
--- a/bsd-user/freebsd/os-proc.h
+++ b/bsd-user/freebsd/os-proc.h
@@ -208,7 +208,7 @@ static inline abi_long do_freebsd_fork(void *cpu_env)
*/
set_second_rval(cpu_env, child_flag);
- fork_end(child_flag);
+ fork_end(ret);
return ret;
}
@@ -252,7 +252,7 @@ static inline abi_long do_freebsd_rfork(void *cpu_env, abi_long flags)
* value: 0 for parent process, 1 for child process.
*/
set_second_rval(cpu_env, child_flag);
- fork_end(child_flag);
+ fork_end(ret);
return ret;
@@ -285,7 +285,7 @@ static inline abi_long do_freebsd_pdfork(void *cpu_env, abi_ulong target_fdp,
* value: 0 for parent process, 1 for child process.
*/
set_second_rval(cpu_env, child_flag);
- fork_end(child_flag);
+ fork_end(ret);
return ret;
}
diff --git a/bsd-user/main.c b/bsd-user/main.c
index bfe6888ea89..bc233a85cef 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -109,8 +109,10 @@ void fork_start(void)
gdbserver_fork_start();
}
-void fork_end(int child)
+void fork_end(abi_long pid)
{
+ int child = pid == 0;
+
if (child) {
CPUState *cpu, *next_cpu;
/*
diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h
index dc842fffa7d..2414a87559b 100644
--- a/bsd-user/qemu.h
+++ b/bsd-user/qemu.h
@@ -180,7 +180,7 @@ void cpu_loop(CPUArchState *env);
char *target_strerror(int err);
int get_osversion(void);
void fork_start(void);
-void fork_end(int child);
+void fork_end(abi_long pid);
#include "qemu/log.h"
diff --git a/linux-user/main.c b/linux-user/main.c
index 8c7bea1c631..f1a0267816b 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -147,8 +147,10 @@ void fork_start(void)
gdbserver_fork_start();
}
-void fork_end(int child)
+void fork_end(abi_long pid)
{
+ int child = pid == 0;
+
qemu_plugin_user_postfork(child);
mmap_fork_end(child);
if (child) {
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index aeb95644873..9e628a0f975 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -6669,7 +6669,7 @@ static int do_fork(CPUArchState *env, unsigned int flags, abi_ulong newsp,
if (ret == 0) {
/* Child Process. */
cpu_clone_regs_child(env, newsp, flags);
- fork_end(1);
+ fork_end(ret);
/* There is a race condition here. The parent process could
theoretically read the TID in the child process before the child
tid is set. This would require using either ptrace
@@ -6700,8 +6700,8 @@ static int do_fork(CPUArchState *env, unsigned int flags, abi_ulong newsp,
}
#endif
put_user_u32(pid_fd, parent_tidptr);
- }
- fork_end(0);
+ }
+ fork_end(ret);
}
g_assert(!cpu_in_exclusive_context(cpu));
}
diff --git a/linux-user/user-internals.h b/linux-user/user-internals.h
index c63ef45fc78..9014014d920 100644
--- a/linux-user/user-internals.h
+++ b/linux-user/user-internals.h
@@ -71,7 +71,7 @@ const char *target_strerror(int err);
int get_osversion(void);
void init_qemu_uname_release(void);
void fork_start(void);
-void fork_end(int child);
+void fork_end(abi_long pid);
/**
* probe_guest_base:
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 05/11] {linux,bsd}-user: Pass pid to gdbserver_fork()
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
` (3 preceding siblings ...)
2024-02-05 11:22 ` [PATCH v2 04/11] {linux,bsd}-user: Pass pid to fork_end() Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 06/11] gdbstub: Call gdbserver_fork() both in parent and in child Ilya Leoshkevich
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
The upcoming follow-fork-mode child support requires knowing the child
pid. Pass it down.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
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(-)
diff --git a/bsd-user/main.c b/bsd-user/main.c
index bc233a85cef..e8c658eda5d 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -131,7 +131,7 @@ void fork_end(abi_long pid)
*/
qemu_init_cpu_list();
((TaskState *)thread_cpu->opaque)->ts_tid = qemu_get_thread_id();
- gdbserver_fork(thread_cpu);
+ gdbserver_fork(pid);
} else {
mmap_fork_end(child);
cpu_list_unlock();
diff --git a/gdbstub/user.c b/gdbstub/user.c
index 85fd44b6aa9..d3a749f3e7e 100644
--- a/gdbstub/user.c
+++ b/gdbstub/user.c
@@ -375,7 +375,7 @@ static void disable_gdbstub(void)
}
/* Disable gdb stub for child processes. */
-void gdbserver_fork(CPUState *cpu)
+void gdbserver_fork(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 e33f8d9a9a6..66dd0d319cf 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(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 f1a0267816b..ad1c6394520 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -164,7 +164,7 @@ void fork_end(abi_long pid)
}
qemu_init_cpu_list();
((TaskState *)thread_cpu->opaque)->ts_tid = qemu_get_thread_id();
- gdbserver_fork(thread_cpu);
+ gdbserver_fork(pid);
} else {
cpu_list_unlock();
}
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 06/11] gdbstub: Call gdbserver_fork() both in parent and in child
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
` (4 preceding siblings ...)
2024-02-05 11:22 ` [PATCH v2 05/11] {linux,bsd}-user: Pass pid to gdbserver_fork() Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 07/11] gdbstub: Introduce gdb_handle_query_supported_user() Ilya Leoshkevich
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
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.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
bsd-user/main.c | 3 ++-
gdbstub/user.c | 5 ++---
include/gdbstub/user.h | 6 +++---
linux-user/main.c | 2 +-
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/bsd-user/main.c b/bsd-user/main.c
index e8c658eda5d..1890d6365f7 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -131,10 +131,11 @@ void fork_end(abi_long pid)
*/
qemu_init_cpu_list();
((TaskState *)thread_cpu->opaque)->ts_tid = qemu_get_thread_id();
- gdbserver_fork(pid);
+ gdbserver_fork_end(pid);
} else {
mmap_fork_end(child);
cpu_list_unlock();
+ gdbserver_fork_end(pid);
end_exclusive();
}
}
diff --git a/gdbstub/user.c b/gdbstub/user.c
index d3a749f3e7e..df5a618e789 100644
--- a/gdbstub/user.c
+++ b/gdbstub/user.c
@@ -374,10 +374,9 @@ static void disable_gdbstub(void)
}
}
-/* Disable gdb stub for child processes. */
-void gdbserver_fork(pid_t pid)
+void gdbserver_fork_end(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();
diff --git a/include/gdbstub/user.h b/include/gdbstub/user.h
index 66dd0d319cf..dd03dbdd6df 100644
--- a/include/gdbstub/user.h
+++ b/include/gdbstub/user.h
@@ -51,10 +51,10 @@ void gdb_signalled(CPUArchState *as, int sig);
void gdbserver_fork_start(void);
/**
- * gdbserver_fork() - disable gdb stub for child processes.
- * @cs: CPU
+ * gdbserver_fork_end() - inform gdb of the completed fork()
+ * @pid: 0 if in child process, -1 if fork failed, child process pid otherwise
*/
-void gdbserver_fork(pid_t pid);
+void gdbserver_fork_end(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 ad1c6394520..dde5081e2f4 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -164,10 +164,10 @@ void fork_end(abi_long pid)
}
qemu_init_cpu_list();
((TaskState *)thread_cpu->opaque)->ts_tid = qemu_get_thread_id();
- gdbserver_fork(pid);
} else {
cpu_list_unlock();
}
+ gdbserver_fork_end(pid);
/*
* qemu_init_cpu_list() reinitialized the child exclusive state, but we
* also need to keep current_cpu consistent, so call end_exclusive() for
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 07/11] gdbstub: Introduce gdb_handle_query_supported_user()
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
` (5 preceding siblings ...)
2024-02-05 11:22 ` [PATCH v2 06/11] gdbstub: Call gdbserver_fork() both in parent and in child Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 08/11] gdbstub: Introduce gdb_handle_set_thread_user() Ilya Leoshkevich
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
The upcoming follow-fork-mode child support requires advertising the
fork-events feature, which is user-specific. Introduce a user-specific
hook for this.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
gdbstub/gdbstub.c | 12 +++++++++---
gdbstub/internals.h | 1 +
gdbstub/user.c | 4 ++++
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c
index 7e73e916bdc..43d79dfdd59 100644
--- a/gdbstub/gdbstub.c
+++ b/gdbstub/gdbstub.c
@@ -1599,6 +1599,7 @@ static void handle_query_thread_extra(GArray *params, void *user_ctx)
static void handle_query_supported(GArray *params, void *user_ctx)
{
+ const char *gdb_supported;
CPUClass *cc;
g_string_printf(gdbserver_state.str_buf, "PacketSize=%x", MAX_PACKET_LENGTH);
@@ -1622,9 +1623,14 @@ 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) {
+ 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 56b7c13b750..e6063835b1f 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 df5a618e789..2b8c67972c0 100644
--- a/gdbstub/user.c
+++ b/gdbstub/user.c
@@ -382,6 +382,10 @@ void gdbserver_fork_end(pid_t pid)
disable_gdbstub();
}
+void gdb_handle_query_supported_user(const char *gdb_supported)
+{
+}
+
/*
* Execution state helpers
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 08/11] gdbstub: Introduce gdb_handle_set_thread_user()
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
` (6 preceding siblings ...)
2024-02-05 11:22 ` [PATCH v2 07/11] gdbstub: Introduce gdb_handle_query_supported_user() Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 09/11] gdbstub: Introduce gdb_handle_detach_user() Ilya Leoshkevich
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
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 <iii@linux.ibm.com>
---
gdbstub/gdbstub.c | 11 +++++++++--
gdbstub/internals.h | 1 +
gdbstub/user.c | 5 +++++
3 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c
index 43d79dfdd59..adcd977cd57 100644
--- a/gdbstub/gdbstub.c
+++ b/gdbstub/gdbstub.c
@@ -1066,6 +1066,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) {
@@ -1083,8 +1084,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 e6063835b1f..b4905c7181a 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 2b8c67972c0..ee6b47b9b9c 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
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 09/11] gdbstub: Introduce gdb_handle_detach_user()
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
` (7 preceding siblings ...)
2024-02-05 11:22 ` [PATCH v2 08/11] gdbstub: Introduce gdb_handle_set_thread_user() Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 10/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 11/11] tests/tcg: Add two follow-fork-mode tests Ilya Leoshkevich
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
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 <iii@linux.ibm.com>
---
gdbstub/gdbstub.c | 6 ++++++
gdbstub/internals.h | 1 +
gdbstub/user.c | 5 +++++
3 files changed, 12 insertions(+)
diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c
index adcd977cd57..46f5dd47e9e 100644
--- a/gdbstub/gdbstub.c
+++ b/gdbstub/gdbstub.c
@@ -991,6 +991,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 b4905c7181a..b4724598384 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 ee6b47b9b9c..6f108e60839 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
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 10/11] gdbstub: Implement follow-fork-mode child
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
` (8 preceding siblings ...)
2024-02-05 11:22 ` [PATCH v2 09/11] gdbstub: Introduce gdb_handle_detach_user() Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 11/11] tests/tcg: Add two follow-fork-mode tests Ilya Leoshkevich
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
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 <iii@linux.ibm.com>
---
gdbstub/user.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 210 insertions(+), 2 deletions(-)
diff --git a/gdbstub/user.c b/gdbstub/user.c
index 6f108e60839..bb1e384ddb1 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(void)
@@ -376,23 +447,160 @@ static void disable_gdbstub(void)
void gdbserver_fork_end(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();
+
+ 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();
+ }
+ 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();
+ return;
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+fail:
+ close(fd);
+ if (pid == 0) {
+ disable_gdbstub();
+ }
}
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;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 11/11] tests/tcg: Add two follow-fork-mode tests
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
` (9 preceding siblings ...)
2024-02-05 11:22 ` [PATCH v2 10/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
@ 2024-02-05 11:22 ` Ilya Leoshkevich
10 siblings, 0 replies; 12+ messages in thread
From: Ilya Leoshkevich @ 2024-02-05 11:22 UTC (permalink / raw)
To: Warner Losh, Alex Bennée, Laurent Vivier
Cc: Kyle Evans, Philippe Mathieu-Daudé, qemu-devel,
Ilya Leoshkevich
Add follow-fork-mode child and and follow-fork-mode parent tests.
Check for the obvious pitfalls, such as lingering breakpoints,
catchpoints, and single-step mode.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
---
tests/tcg/multiarch/Makefile.target | 17 +++++-
tests/tcg/multiarch/follow-fork-mode.c | 56 +++++++++++++++++++
.../gdbstub/follow-fork-mode-child.py | 40 +++++++++++++
.../gdbstub/follow-fork-mode-parent.py | 16 ++++++
4 files changed, 128 insertions(+), 1 deletion(-)
create mode 100644 tests/tcg/multiarch/follow-fork-mode.c
create mode 100644 tests/tcg/multiarch/gdbstub/follow-fork-mode-child.py
create mode 100644 tests/tcg/multiarch/gdbstub/follow-fork-mode-parent.py
diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
index e10951a8016..b8b70c81860 100644
--- a/tests/tcg/multiarch/Makefile.target
+++ b/tests/tcg/multiarch/Makefile.target
@@ -115,6 +115,20 @@ run-gdbstub-catch-syscalls: catch-syscalls
--bin $< --test $(MULTIARCH_SRC)/gdbstub/catch-syscalls.py, \
hitting a syscall catchpoint)
+run-gdbstub-follow-fork-mode-child: follow-fork-mode
+ $(call run-test, $@, $(GDB_SCRIPT) \
+ --gdb $(GDB) \
+ --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \
+ --bin $< --test $(MULTIARCH_SRC)/gdbstub/follow-fork-mode-child.py, \
+ following children on fork)
+
+run-gdbstub-follow-fork-mode-parent: follow-fork-mode
+ $(call run-test, $@, $(GDB_SCRIPT) \
+ --gdb $(GDB) \
+ --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \
+ --bin $< --test $(MULTIARCH_SRC)/gdbstub/follow-fork-mode-parent.py, \
+ following parents on fork)
+
else
run-gdbstub-%:
$(call skip-test, "gdbstub test $*", "need working gdb with $(patsubst -%,,$(TARGET_NAME)) support")
@@ -122,7 +136,8 @@ endif
EXTRA_RUNS += run-gdbstub-sha1 run-gdbstub-qxfer-auxv-read \
run-gdbstub-proc-mappings run-gdbstub-thread-breakpoint \
run-gdbstub-registers run-gdbstub-prot-none \
- run-gdbstub-catch-syscalls
+ run-gdbstub-catch-syscalls run-gdbstub-follow-fork-mode-child \
+ run-gdbstub-follow-fork-mode-parent
# ARM Compatible Semi Hosting Tests
#
diff --git a/tests/tcg/multiarch/follow-fork-mode.c b/tests/tcg/multiarch/follow-fork-mode.c
new file mode 100644
index 00000000000..cb6b032b388
--- /dev/null
+++ b/tests/tcg/multiarch/follow-fork-mode.c
@@ -0,0 +1,56 @@
+/*
+ * Test GDB's follow-fork-mode.
+ *
+ * fork() a chain of processes.
+ * Parents sends one byte to their children, and children return their
+ * position in the chain, in order to prove that they survived GDB's fork()
+ * handling.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <assert.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+void break_after_fork(void)
+{
+}
+
+int main(void)
+{
+ int depth = 42, err, i, fd[2], status;
+ pid_t child, pid;
+ ssize_t n;
+ char b;
+
+ for (i = 0; i < depth; i++) {
+ err = pipe(fd);
+ assert(err == 0);
+ child = fork();
+ break_after_fork();
+ assert(child != -1);
+ if (child == 0) {
+ close(fd[1]);
+
+ n = read(fd[0], &b, 1);
+ close(fd[0]);
+ assert(n == 1);
+ assert(b == (char)i);
+ } else {
+ close(fd[0]);
+
+ b = (char)i;
+ n = write(fd[1], &b, 1);
+ close(fd[1]);
+ assert(n == 1);
+
+ pid = waitpid(child, &status, 0);
+ assert(pid == child);
+ assert(WIFEXITED(status));
+ return WEXITSTATUS(status) - 1;
+ }
+ }
+
+ return depth;
+}
diff --git a/tests/tcg/multiarch/gdbstub/follow-fork-mode-child.py b/tests/tcg/multiarch/gdbstub/follow-fork-mode-child.py
new file mode 100644
index 00000000000..72a6e440c08
--- /dev/null
+++ b/tests/tcg/multiarch/gdbstub/follow-fork-mode-child.py
@@ -0,0 +1,40 @@
+"""Test GDB's follow-fork-mode child.
+
+SPDX-License-Identifier: GPL-2.0-or-later
+"""
+from test_gdbstub import main, report
+
+
+def run_test():
+ """Run through the tests one by one"""
+ gdb.execute("set follow-fork-mode child")
+ # Check that the parent breakpoints are unset.
+ gdb.execute("break break_after_fork")
+ # Check that the parent syscall catchpoints are unset.
+ # Skip this check on the architectures that don't have them.
+ have_fork_syscall = False
+ for fork_syscall in ("fork", "clone", "clone2", "clone3"):
+ try:
+ gdb.execute("catch syscall {}".format(fork_syscall))
+ except gdb.error:
+ pass
+ else:
+ have_fork_syscall = True
+ gdb.execute("continue")
+ for i in range(42):
+ if have_fork_syscall:
+ # syscall entry.
+ if i % 2 == 0:
+ # Check that the parent single-stepping is turned off.
+ gdb.execute("si")
+ else:
+ gdb.execute("continue")
+ # syscall exit.
+ gdb.execute("continue")
+ # break_after_fork()
+ gdb.execute("continue")
+ exitcode = int(gdb.parse_and_eval("$_exitcode"))
+ report(exitcode == 42, "{} == 42".format(exitcode))
+
+
+main(run_test)
diff --git a/tests/tcg/multiarch/gdbstub/follow-fork-mode-parent.py b/tests/tcg/multiarch/gdbstub/follow-fork-mode-parent.py
new file mode 100644
index 00000000000..5c2fe722088
--- /dev/null
+++ b/tests/tcg/multiarch/gdbstub/follow-fork-mode-parent.py
@@ -0,0 +1,16 @@
+"""Test GDB's follow-fork-mode parent.
+
+SPDX-License-Identifier: GPL-2.0-or-later
+"""
+from test_gdbstub import main, report
+
+
+def run_test():
+ """Run through the tests one by one"""
+ gdb.execute("set follow-fork-mode parent")
+ gdb.execute("continue")
+ exitcode = int(gdb.parse_and_eval("$_exitcode"))
+ report(exitcode == 0, "{} == 0".format(exitcode))
+
+
+main(run_test)
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
end of thread, other threads:[~2024-02-05 11:25 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-02-05 11:22 [PATCH v2 00/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 01/11] gdbstub: Support disablement in a multi-threaded process Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 02/11] {linux,bsd}-user: Update ts_tid after fork() Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 03/11] gdbstub: Introduce gdbserver_fork_start() Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 04/11] {linux,bsd}-user: Pass pid to fork_end() Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 05/11] {linux,bsd}-user: Pass pid to gdbserver_fork() Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 06/11] gdbstub: Call gdbserver_fork() both in parent and in child Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 07/11] gdbstub: Introduce gdb_handle_query_supported_user() Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 08/11] gdbstub: Introduce gdb_handle_set_thread_user() Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 09/11] gdbstub: Introduce gdb_handle_detach_user() Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 10/11] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
2024-02-05 11:22 ` [PATCH v2 11/11] tests/tcg: Add two follow-fork-mode tests Ilya Leoshkevich
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).