public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 0/7] amdgpu: handle fork and exec
@ 2023-04-03 18:52 Simon Marchi
  2023-04-03 18:52 ` [PATCH 1/7] gdb: pass execing and following inferior to inferior_execd observers Simon Marchi
                   ` (7 more replies)
  0 siblings, 8 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-03 18:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

This series adds support for fork and exec to the amdgpu port.  This
means making sure that the appropriate cleanups are done when an
inferior using the GPU forks and / or execs, and the appropriates
actions taken so that we can properly debug an inferior using the GPU
post-fork or post-exec.

Simon Marchi (7):
  gdb: pass execing and following inferior to inferior_execd observers
  gdb: add inferior_forked observable
  gdb: remove regcache::target
  gdb: add maybe_switch_inferior function
  gdb: make regcache::raw_update switch to right inferior
  gdb: switch to right inferior in fetch_inferior_event
  gdb/amdgpu: add follow fork and exec support

 gdb/amd-dbgapi-target.c                       | 39 ++++++++
 gdb/gdbthread.h                               |  2 +
 gdb/inferior.c                                | 15 ++++
 gdb/inferior.h                                |  7 ++
 gdb/infrun.c                                  | 53 ++++++-----
 gdb/jit.c                                     | 20 +++--
 gdb/linux-tdep.c                              | 10 ++-
 gdb/observable.c                              |  1 +
 gdb/observable.h                              | 17 +++-
 gdb/record-btrace.c                           |  3 +-
 gdb/regcache.c                                | 90 ++++++++++++-------
 gdb/regcache.h                                | 22 +++--
 gdb/solib.c                                   |  3 +-
 .../fork-exec-gpu-to-non-gpu-execee.cpp       | 27 ++++++
 .../fork-exec-gpu-to-non-gpu-execer.cpp       | 55 ++++++++++++
 .../gdb.rocm/fork-exec-gpu-to-non-gpu.exp     | 89 ++++++++++++++++++
 .../fork-exec-non-gpu-to-gpu-execee.cpp       | 36 ++++++++
 .../fork-exec-non-gpu-to-gpu-execer.cpp       | 46 ++++++++++
 .../gdb.rocm/fork-exec-non-gpu-to-gpu.exp     | 88 ++++++++++++++++++
 gdb/thread.c                                  | 14 +++
 20 files changed, 561 insertions(+), 76 deletions(-)
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execee.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execer.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu.exp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execee.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execer.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu.exp

-- 
2.40.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/7] gdb: pass execing and following inferior to inferior_execd observers
  2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
@ 2023-04-03 18:52 ` Simon Marchi
  2023-04-03 18:52 ` [PATCH 2/7] gdb: add inferior_forked observable Simon Marchi
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-03 18:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi, Pedro Alves

The upcoming patch to support exec in the amd-dbgapi target needs to
detach amd-dbgapi from the inferior doing the exec and attach amd-dbgapi
to the inferior continuing the execution.  They may or may not be the
same, depending on the `set follow-exec-mode` setting.  But even if they
are the same, we need to do the detach / attach dance.

With the current observable signature, the observers only receive the
inferior in which execution continues (the "following" inferior).

Change the signature to pass both inferiors, and update all existing
observers.

Change-Id: I259d1ea09f70f43be739378d6023796f2fce2659
Reviewed-By: Pedro Alves <pedro@palves.net>
---
 gdb/infrun.c     | 39 +++++++++++++++++++++------------------
 gdb/jit.c        | 20 ++++++++++++++------
 gdb/linux-tdep.c | 10 +++++++++-
 gdb/observable.h |  8 ++++++--
 gdb/solib.c      |  3 ++-
 5 files changed, 52 insertions(+), 28 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 87141117dfe3..c95a8e7ee1da 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1293,7 +1293,8 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
      previous incarnation of this process.  */
   no_shared_libraries (nullptr, 0);
 
-  struct inferior *inf = current_inferior ();
+  inferior *execing_inferior = current_inferior ();
+  inferior *following_inferior;
 
   if (follow_exec_mode_string == follow_exec_mode_new)
     {
@@ -1304,19 +1305,19 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
 	 inferior's pid.  Having two inferiors with the same pid would confuse
 	 find_inferior_p(t)id.  Transfer the terminal state and info from the
 	  old to the new inferior.  */
-      inferior *new_inferior = add_inferior_with_spaces ();
-
-      swap_terminal_info (new_inferior, inf);
-      exit_inferior_silent (inf);
+      following_inferior = add_inferior_with_spaces ();
 
-      new_inferior->pid = pid;
-      target_follow_exec (new_inferior, ptid, exec_file_target);
+      swap_terminal_info (following_inferior, execing_inferior);
+      exit_inferior_silent (execing_inferior);
 
-      /* We continue with the new inferior.  */
-      inf = new_inferior;
+      following_inferior->pid = pid;
     }
   else
     {
+      /* follow-exec-mode is "same", we continue execution in the execing
+	 inferior.  */
+      following_inferior = execing_inferior;
+
       /* The old description may no longer be fit for the new image.
 	 E.g, a 64-bit process exec'ed a 32-bit process.  Clear the
 	 old description; we'll read a new one below.  No need to do
@@ -1324,18 +1325,20 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
 	 around (its description is later cleared/refetched on
 	 restart).  */
       target_clear_description ();
-      target_follow_exec (inf, ptid, exec_file_target);
     }
 
-  gdb_assert (current_inferior () == inf);
-  gdb_assert (current_program_space == inf->pspace);
+  target_follow_exec (following_inferior, ptid, exec_file_target);
+
+  gdb_assert (current_inferior () == following_inferior);
+  gdb_assert (current_program_space == following_inferior->pspace);
 
   /* Attempt to open the exec file.  SYMFILE_DEFER_BP_RESET is used
      because the proper displacement for a PIE (Position Independent
      Executable) main symbol file will only be computed by
      solib_create_inferior_hook below.  breakpoint_re_set would fail
      to insert the breakpoints with the zero displacement.  */
-  try_open_exec_file (exec_file_host.get (), inf, SYMFILE_DEFER_BP_RESET);
+  try_open_exec_file (exec_file_host.get (), following_inferior,
+		      SYMFILE_DEFER_BP_RESET);
 
   /* If the target can specify a description, read it.  Must do this
      after flipping to the new executable (because the target supplied
@@ -1345,7 +1348,7 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
      registers.  */
   target_find_description ();
 
-  gdb::observers::inferior_execd.notify (inf);
+  gdb::observers::inferior_execd.notify (execing_inferior, following_inferior);
 
   breakpoint_re_set ();
 
@@ -1622,15 +1625,15 @@ infrun_inferior_exit (struct inferior *inf)
 }
 
 static void
-infrun_inferior_execd (inferior *inf)
+infrun_inferior_execd (inferior *exec_inf, inferior *follow_inf)
 {
   /* If some threads where was doing a displaced step in this inferior at the
      moment of the exec, they no longer exist.  Even if the exec'ing thread
      doing a displaced step, we don't want to to any fixup nor restore displaced
      stepping buffer bytes.  */
-  inf->displaced_step_state.reset ();
+  follow_inf->displaced_step_state.reset ();
 
-  for (thread_info *thread : inf->threads ())
+  for (thread_info *thread : follow_inf->threads ())
     thread->displaced_step_state.reset ();
 
   /* Since an in-line step is done with everything else stopped, if there was
@@ -1638,7 +1641,7 @@ infrun_inferior_execd (inferior *inf)
      thread.  */
   clear_step_over_info ();
 
-  inf->thread_waiting_for_vfork_done = nullptr;
+  follow_inf->thread_waiting_for_vfork_done = nullptr;
 }
 
 /* If ON, and the architecture supports it, GDB will use displaced
diff --git a/gdb/jit.c b/gdb/jit.c
index e276b3417a04..e085d5623336 100644
--- a/gdb/jit.c
+++ b/gdb/jit.c
@@ -1147,7 +1147,10 @@ jit_prepend_unwinder (struct gdbarch *gdbarch)
     }
 }
 
-/* Register any already created translations.  */
+/* Looks for the descriptor and registration symbols and breakpoints
+   the registration function.  If it finds both, it registers all the
+   already JITed code.  If it has already found the symbols, then it
+   doesn't try again.  */
 
 static void
 jit_inferior_init (inferior *inf)
@@ -1203,10 +1206,7 @@ jit_inferior_init (inferior *inf)
     }
 }
 
-/* Looks for the descriptor and registration symbols and breakpoints
-   the registration function.  If it finds both, it registers all the
-   already JITed code.  If it has already found the symbols, then it
-   doesn't try again.  */
+/* inferior_created observer.  */
 
 static void
 jit_inferior_created_hook (inferior *inf)
@@ -1214,6 +1214,14 @@ jit_inferior_created_hook (inferior *inf)
   jit_inferior_init (inf);
 }
 
+/* inferior_execd observer.  */
+
+static void
+jit_inferior_execd_hook (inferior *exec_inf, inferior *follow_inf)
+{
+  jit_inferior_init (follow_inf);
+}
+
 /* Exported routine to call to re-set the jit breakpoints,
    e.g. when a program is rerun.  */
 
@@ -1304,7 +1312,7 @@ _initialize_jit ()
 	   &maintenanceinfolist);
 
   gdb::observers::inferior_created.attach (jit_inferior_created_hook, "jit");
-  gdb::observers::inferior_execd.attach (jit_inferior_created_hook, "jit");
+  gdb::observers::inferior_execd.attach (jit_inferior_execd_hook, "jit");
   gdb::observers::inferior_exit.attach (jit_inferior_exit_hook, "jit");
   gdb::observers::breakpoint_deleted.attach (jit_breakpoint_deleted, "jit");
 
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 1fc9cb6faee9..b5eee5e108ce 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -244,6 +244,14 @@ invalidate_linux_cache_inf (struct inferior *inf)
   linux_inferior_data.clear (inf);
 }
 
+/* inferior_execd observer.  */
+
+static void
+linux_inferior_execd (inferior *exec_inf, inferior *follow_inf)
+{
+  invalidate_linux_cache_inf (follow_inf);
+}
+
 /* Fetch the linux cache info for INF.  This function always returns a
    valid INFO pointer.  */
 
@@ -2789,7 +2797,7 @@ _initialize_linux_tdep ()
 					"linux-tdep");
   gdb::observers::inferior_appeared.attach (invalidate_linux_cache_inf,
 					    "linux-tdep");
-  gdb::observers::inferior_execd.attach (invalidate_linux_cache_inf,
+  gdb::observers::inferior_execd.attach (linux_inferior_execd,
 					 "linux-tdep");
 
   add_setshow_boolean_cmd ("use-coredump-filter", class_files,
diff --git a/gdb/observable.h b/gdb/observable.h
index efd0446e1689..00955cbc876b 100644
--- a/gdb/observable.h
+++ b/gdb/observable.h
@@ -90,8 +90,12 @@ extern observable<> executable_changed;
    information on the inferior has been printed.  */
 extern observable<inferior */* inferior */> inferior_created;
 
-/* The inferior INF has exec'ed a new executable file.  */
-extern observable<struct inferior */* inf */> inferior_execd;
+/* The inferior EXEC_INF has exec'ed a new executable file.
+
+   Execution continues in FOLLOW_INF, which may or may not be the same as
+   EXEC_INF, depending on "set follow-exec-mode".  */
+extern observable<inferior */* exec_inf */, inferior */* follow_inf */>
+    inferior_execd;
 
 /* The status of process record for inferior inferior in gdb has
    changed.  The process record is started if STARTED is true, and
diff --git a/gdb/solib.c b/gdb/solib.c
index 09bee497fd6e..16147830ef2c 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -1744,7 +1744,8 @@ _initialize_solib ()
 {
   gdb::observers::free_objfile.attach (remove_user_added_objfile,
 				       "solib");
-  gdb::observers::inferior_execd.attach ([] (inferior *inf)
+  gdb::observers::inferior_execd.attach ([] (inferior *exec_inf,
+					     inferior *follow_inf)
     {
       solib_create_inferior_hook (0);
     }, "solib");
-- 
2.40.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 2/7] gdb: add inferior_forked observable
  2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
  2023-04-03 18:52 ` [PATCH 1/7] gdb: pass execing and following inferior to inferior_execd observers Simon Marchi
@ 2023-04-03 18:52 ` Simon Marchi
  2023-04-03 18:52 ` [PATCH 3/7] gdb: remove regcache::target Simon Marchi
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-03 18:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi, Pedro Alves

In the upcoming patch to support fork in the amd-dbgapi target, the
amd-dbgapi target will need to be notified of fork events through an
observer, to attach itself (attach in the amd-dbgapi sense, not ptrace
sense) to the new inferior / process.

The reason that this can't be done through target_ops::follow_fork is
that the amd-dbgapi target isn't pushed on the inferior's target stack
right away.  It attaches itself to the process and only pushes itself on
its target stack if and when the inferior initializes the ROCm runtime.

If an inferior that is not using the ROCm runtime forks, we want to be
notified of it, so we can attach to the child, and catch if the child
starts using the ROCm runtime.

So, add a new observable and notify it in follow_fork_inferior.  It will
be used later in this series.

Change-Id: I67fced5a9cba6d5da72b9c7ea1c8397644ca1d54
Reviewed-By: Pedro Alves <pedro@palves.net>
---
 gdb/infrun.c     | 2 ++
 gdb/observable.c | 1 +
 gdb/observable.h | 9 +++++++++
 3 files changed, 12 insertions(+)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index c95a8e7ee1da..11a788467a8a 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -624,6 +624,8 @@ holding the child stopped.  Try \"set detach-on-fork\" or \
   target_follow_fork (child_inf, child_ptid, fork_kind, follow_child,
 		      detach_fork);
 
+  gdb::observers::inferior_forked.notify (parent_inf, child_inf, fork_kind);
+
   /* target_follow_fork must leave the parent as the current inferior.  If we
      want to follow the child, we make it the current one below.  */
   gdb_assert (current_inferior () == parent_inf);
diff --git a/gdb/observable.c b/gdb/observable.c
index 28249a5ad015..49de89c25e04 100644
--- a/gdb/observable.c
+++ b/gdb/observable.c
@@ -44,6 +44,7 @@ DEFINE_OBSERVABLE (target_changed);
 DEFINE_OBSERVABLE (executable_changed);
 DEFINE_OBSERVABLE (inferior_created);
 DEFINE_OBSERVABLE (inferior_execd);
+DEFINE_OBSERVABLE (inferior_forked);
 DEFINE_OBSERVABLE (record_changed);
 DEFINE_OBSERVABLE (solib_loaded);
 DEFINE_OBSERVABLE (solib_unloaded);
diff --git a/gdb/observable.h b/gdb/observable.h
index 00955cbc876b..3066cf68f314 100644
--- a/gdb/observable.h
+++ b/gdb/observable.h
@@ -21,6 +21,7 @@
 #define OBSERVABLE_H
 
 #include "gdbsupport/observable.h"
+#include "target/waitstatus.h"
 
 struct bpstat;
 struct so_list;
@@ -97,6 +98,14 @@ extern observable<inferior */* inferior */> inferior_created;
 extern observable<inferior */* exec_inf */, inferior */* follow_inf */>
     inferior_execd;
 
+/* The inferior PARENT_INF has forked.  If we are setting up an inferior for
+   the child (because we follow only the child or we follow both), CHILD_INF
+   is the child inferior.  Otherwise, CHILD_INF is nullptr.
+
+   FORK_KIND is TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED.  */
+extern observable<inferior */* parent_inf */, inferior */* child_inf */,
+		  target_waitkind /* fork_kind */> inferior_forked;
+
 /* The status of process record for inferior inferior in gdb has
    changed.  The process record is started if STARTED is true, and
    the process record is stopped if STARTED is false.
-- 
2.40.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 3/7] gdb: remove regcache::target
  2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
  2023-04-03 18:52 ` [PATCH 1/7] gdb: pass execing and following inferior to inferior_execd observers Simon Marchi
  2023-04-03 18:52 ` [PATCH 2/7] gdb: add inferior_forked observable Simon Marchi
@ 2023-04-03 18:52 ` Simon Marchi
  2023-04-03 18:52 ` [PATCH 4/7] gdb: add maybe_switch_inferior function Simon Marchi
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-03 18:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi, Pedro Alves

The regcache class takes a process_stratum_target and then exposes it
through regcache::target.  But it doesn't use it itself, suggesting it
doesn't really make sense to put it there.  The only user of
regcache::target is record_btrace_target::fetch_registers, but it might
as well just get it from the current target stack.  This simplifies a
little bit a patch later in this series.

Change-Id: I8878d875805681c77f469ac1a2bf3a508559a62d
Reviewed-By: Pedro Alves <pedro@palves.net>
---
 gdb/record-btrace.c | 3 ++-
 gdb/regcache.c      | 1 -
 gdb/regcache.h      | 5 -----
 3 files changed, 2 insertions(+), 7 deletions(-)

diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
index 2d88e4d20bf6..358d8de089f8 100644
--- a/gdb/record-btrace.c
+++ b/gdb/record-btrace.c
@@ -1548,7 +1548,8 @@ record_btrace_target::fetch_registers (struct regcache *regcache, int regno)
   /* Thread-db may ask for a thread's registers before GDB knows about the
      thread.  We forward the request to the target beneath in this
      case.  */
-  thread_info *tp = find_thread_ptid (regcache->target (), regcache->ptid ());
+  thread_info *tp = find_thread_ptid (current_inferior ()->process_target (),
+				      regcache->ptid ());
   if (tp != nullptr)
     replay =  tp->btrace.replay;
 
diff --git a/gdb/regcache.c b/gdb/regcache.c
index af76fab1a34f..cfa8a3d78335 100644
--- a/gdb/regcache.c
+++ b/gdb/regcache.c
@@ -1622,7 +1622,6 @@ get_thread_arch_aspace_regcache_and_check (process_stratum_target *target,
     = get_thread_arch_aspace_regcache (target, ptid, arch, aspace);
 
   SELF_CHECK (regcache != NULL);
-  SELF_CHECK (regcache->target () == target);
   SELF_CHECK (regcache->ptid () == ptid);
   SELF_CHECK (regcache->arch () == arch);
   SELF_CHECK (regcache->aspace () == aspace);
diff --git a/gdb/regcache.h b/gdb/regcache.h
index b9ffab9950d2..2bd2f57b8332 100644
--- a/gdb/regcache.h
+++ b/gdb/regcache.h
@@ -416,11 +416,6 @@ class regcache : public detached_regcache
     this->m_ptid = ptid;
   }
 
-  process_stratum_target *target () const
-  {
-    return m_target;
-  }
-
 /* Dump the contents of a register from the register cache to the target
    debug.  */
   void debug_print_register (const char *func, int regno);
-- 
2.40.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 4/7] gdb: add maybe_switch_inferior function
  2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
                   ` (2 preceding siblings ...)
  2023-04-03 18:52 ` [PATCH 3/7] gdb: remove regcache::target Simon Marchi
@ 2023-04-03 18:52 ` Simon Marchi
  2023-04-03 18:52 ` [PATCH 5/7] gdb: make regcache::raw_update switch to right inferior Simon Marchi
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-03 18:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi, Pedro Alves

Add the maybe_switch_inferior function, which ensures that the given
inferior is the current one.  Return an instantiated
scoped_restore_current_thread object only we actually needed to switch
inferior.

Returning a scoped_restore_current_thread requires it to be
move-constructible, so give it a move constructor.

Change-Id: I1231037102ed6166f2530399e8257ad937fb0569
Reviewed-By: Pedro Alves <pedro@palves.net>
---
 gdb/gdbthread.h |  2 ++
 gdb/inferior.c  | 15 +++++++++++++++
 gdb/inferior.h  |  7 +++++++
 gdb/thread.c    | 14 ++++++++++++++
 4 files changed, 38 insertions(+)

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 848daa94410a..731c5e159e9f 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -858,6 +858,8 @@ class scoped_restore_current_thread
   scoped_restore_current_thread ();
   ~scoped_restore_current_thread ();
 
+  scoped_restore_current_thread (scoped_restore_current_thread &&rhs);
+
   DISABLE_COPY_AND_ASSIGN (scoped_restore_current_thread);
 
   /* Cancel restoring on scope exit.  */
diff --git a/gdb/inferior.c b/gdb/inferior.c
index a1e3c79d8a20..f6ed942c5053 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -672,6 +672,21 @@ switch_to_inferior_no_thread (inferior *inf)
   set_current_program_space (inf->pspace);
 }
 
+/* See regcache.h.  */
+
+gdb::optional<scoped_restore_current_thread>
+maybe_switch_inferior (inferior *inf)
+{
+  gdb::optional<scoped_restore_current_thread> maybe_restore_thread;
+  if (inf != current_inferior ())
+    {
+      maybe_restore_thread.emplace ();
+      switch_to_inferior_no_thread (inf);
+    }
+
+  return maybe_restore_thread;
+}
+
 static void
 inferior_command (const char *args, int from_tty)
 {
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 72034cc4ffbc..ab981b7b4b27 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -340,6 +340,13 @@ extern void set_current_inferior (inferior *);
    selected.  */
 extern void switch_to_inferior_no_thread (inferior *inf);
 
+/* Ensure INF is the current inferior.
+
+   If the current inferior was changed, return an RAII object that will
+   restore the original current context.  */
+extern gdb::optional<scoped_restore_current_thread> maybe_switch_inferior
+  (inferior *inf);
+
 /* Info about an inferior's target description.  There's one of these
    for each inferior.  */
 
diff --git a/gdb/thread.c b/gdb/thread.c
index 25d97cd60727..506f8481e17b 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -1403,6 +1403,20 @@ scoped_restore_current_thread::scoped_restore_current_thread ()
     }
 }
 
+scoped_restore_current_thread::scoped_restore_current_thread
+  (scoped_restore_current_thread &&rhs)
+  : m_dont_restore (std::move (rhs.m_dont_restore)),
+    m_thread (std::move (rhs.m_thread)),
+    m_inf (std::move (rhs.m_inf)),
+    m_selected_frame_id (std::move (rhs.m_selected_frame_id)),
+    m_selected_frame_level (std::move (rhs.m_selected_frame_level)),
+    m_was_stopped (std::move (rhs.m_was_stopped)),
+    m_lang (std::move (rhs.m_lang))
+{
+  /* Deactivate the rhs.  */
+  rhs.m_dont_restore = true;
+}
+
 /* See gdbthread.h.  */
 
 int
-- 
2.40.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 5/7] gdb: make regcache::raw_update switch to right inferior
  2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
                   ` (3 preceding siblings ...)
  2023-04-03 18:52 ` [PATCH 4/7] gdb: add maybe_switch_inferior function Simon Marchi
@ 2023-04-03 18:52 ` Simon Marchi
  2023-04-03 18:52 ` [PATCH 6/7] gdb: switch to right inferior in fetch_inferior_event Simon Marchi
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-03 18:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi, Pedro Alves

With the following patch, which teaches the amd-dbgapi target to handle
inferiors that fork, we end up with target stacks in the following
state, when an inferior that does not use the GPU forks an inferior that
eventually uses the GPU.

    inf 1            inf 2
    -----            -----
                     amd-dbgapi
    linux-nat        linux-nat
    exec             exec

When a GPU thread from inferior 2 hits a breakpoint, the following
sequence of events would happen, if it was not for the current patch.

 - we start with inferior 1 as current
 - do_target_wait_1 makes inferior 2 current, does a target_wait, which
   returns a stop event for an amd-dbgapi wave (thread).
 - do_target_wait's scoped_restore_current_thread restores inferior 1 as
   current
 - fetch_inferior_event calls switch_to_target_no_thread with linux-nat
   as the process target, since linux-nat is officially the process
   target of inferior 2.  This makes inferior 1 the current inferior, as
   it's the first inferior with that target.
 - In handle_signal_stop, we have:

    ecs->event_thread->suspend.stop_pc
      = regcache_read_pc (get_thread_regcache (ecs->event_thread));

    context_switch (ecs);

   regcache_read_pc executes while inferior 1 is still the current one
   (because it's before the `context_switch`).  This is a problem,
   because the regcache is for a ptid managed by the amd-dbgapi target
   (e.g. (12345, 1, 1)), a ptid that does not make sense for the
   linux-nat target.  The fetch_registers target call goes directly
   to the linux-nat target, which gets confused.
 - We would then get an error like:

     Couldn't get extended state status: No such process.

   ... since linux-nat tries to do a ptrace call on tid 1.

GDB should switch to the inferior the ptid belongs to before doing the
target call to fetch registers, to make sure the call hits the right
target stack (it should be handled by the amd-dbgapi target in this
case).  In fact the following patch does this change, and it would be
enough to fix this specific problem.

However, I propose to change regcache to make it switch to the right
inferior, if needed, before doing target calls.  That makes the
interface as a whole more independent of the global context.

My first attempt at doing this was to find an inferior using the process
stratum target and the ptid that regcache already knows about:

      gdb::optional<scoped_restore_current_thread> restore_thread;
      inferior *inf = find_inferior_ptid (this->target (), this->ptid ());
      if (inf != current_inferior ())
	{
	  restore_thread.emplace ();
	  switch_to_inferior_no_thread (inf);
	}

However, this caused some failures in fork-related tests and gdbserver
boards.  When we detach a fork child, we may create a regcache for the
child, but there is no corresponding inferior.  For instance, to restore
the PC after a displaced step over the fork syscall.  So
find_inferior_ptid would return nullptr, and
switch_to_inferior_no_thread would hit a failed assertion.

So, this patch adds to regcache the information "the inferior to switch
to to makes target calls".  In typical cases, it will be the inferior
that matches the regcache's ptid.  But in some cases, like the detached
fork child one, it will be another inferior (in this example, it will be
the fork parent inferior).

The problem that we witnessed was in regcache::raw_update specifically,
but I looked for other regcache methods doing target calls, and added
the same inferior switching code to raw_write too.

In the regcache constructor and in get_thread_arch_aspace_regcache,
"inf_for_target_calls" replaces the process_stratum_target parameter.
We suppose that the process stratum target that would be passed
otherwise is the same that is in inf_for_target_calls's target stack, so
we don't need to pass both in parallel.  The process stratum target is
still used as a key in the `target_pid_ptid_regcache_map` map, but
that's it.

There is one spot that needs to be updated outside of the regcache code,
which is the path that handles the "restore PC after a displaced step in
a fork child we're about to detach" case mentioned above.

regcache_test_data needs to be changed to include full-fledged mock
contexts (because there now needs to be inferiors, not just targets).

Change-Id: Id088569ce106e1f194d9ae7240ff436f11c5e123
Reviewed-By: Pedro Alves <pedro@palves.net>
---
 gdb/infrun.c   |  2 +-
 gdb/regcache.c | 89 +++++++++++++++++++++++++++++++-------------------
 gdb/regcache.h | 17 +++++++---
 3 files changed, 70 insertions(+), 38 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 11a788467a8a..f32e037f3649 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -5805,7 +5805,7 @@ handle_inferior_event (struct execution_control_state *ecs)
 	       list yet at this point.  */
 
 	    child_regcache
-	      = get_thread_arch_aspace_regcache (parent_inf->process_target (),
+	      = get_thread_arch_aspace_regcache (parent_inf,
 						 ecs->ws.child_ptid (),
 						 gdbarch,
 						 parent_inf->aspace);
diff --git a/gdb/regcache.c b/gdb/regcache.c
index cfa8a3d78335..56292fbd4bff 100644
--- a/gdb/regcache.c
+++ b/gdb/regcache.c
@@ -208,11 +208,12 @@ reg_buffer::reg_buffer (gdbarch *gdbarch, bool has_pseudo)
     }
 }
 
-regcache::regcache (process_stratum_target *target, gdbarch *gdbarch,
+regcache::regcache (inferior *inf_for_target_calls, gdbarch *gdbarch,
 		    const address_space *aspace_)
 /* The register buffers.  A read/write register cache can only hold
    [0 .. gdbarch_num_regs).  */
-  : detached_regcache (gdbarch, false), m_aspace (aspace_), m_target (target)
+  : detached_regcache (gdbarch, false), m_aspace (aspace_),
+    m_inf_for_target_calls (inf_for_target_calls)
 {
   m_ptid = minus_one_ptid;
 }
@@ -348,14 +349,17 @@ using target_pid_ptid_regcache_map
 static target_pid_ptid_regcache_map regcaches;
 
 struct regcache *
-get_thread_arch_aspace_regcache (process_stratum_target *target,
+get_thread_arch_aspace_regcache (inferior *inf_for_target_calls,
 				 ptid_t ptid, gdbarch *arch,
 				 struct address_space *aspace)
 {
-  gdb_assert (target != nullptr);
+  gdb_assert (inf_for_target_calls != nullptr);
+
+  process_stratum_target *proc_target = inf_for_target_calls->process_target ();
+  gdb_assert (proc_target != nullptr);
 
   /* Find the map for this target.  */
-  pid_ptid_regcache_map &pid_ptid_regc_map = regcaches[target];
+  pid_ptid_regcache_map &pid_ptid_regc_map = regcaches[proc_target];
 
   /* Find the map for this pid.  */
   ptid_regcache_map &ptid_regc_map = pid_ptid_regc_map[ptid.pid ()];
@@ -369,7 +373,7 @@ get_thread_arch_aspace_regcache (process_stratum_target *target,
     }
 
   /* It does not exist, create it.  */
-  regcache *new_regcache = new regcache (target, arch, aspace);
+  regcache *new_regcache = new regcache (inf_for_target_calls, arch, aspace);
   new_regcache->set_ptid (ptid);
   /* Work around a problem with g++ 4.8 (PR96537): Call the regcache_up
      constructor explictly instead of implicitly.  */
@@ -383,10 +387,11 @@ get_thread_arch_regcache (process_stratum_target *target, ptid_t ptid,
 			  struct gdbarch *gdbarch)
 {
   scoped_restore_current_inferior restore_current_inferior;
-  set_current_inferior (find_inferior_ptid (target, ptid));
+  inferior *inf = find_inferior_ptid (target, ptid);
+  set_current_inferior (inf);
   address_space *aspace = target_thread_address_space (ptid);
 
-  return get_thread_arch_aspace_regcache (target, ptid, gdbarch, aspace);
+  return get_thread_arch_aspace_regcache (inf, ptid, gdbarch, aspace);
 }
 
 static process_stratum_target *current_thread_target;
@@ -591,6 +596,9 @@ regcache::raw_update (int regnum)
 
   if (get_register_status (regnum) == REG_UNKNOWN)
     {
+      gdb::optional<scoped_restore_current_thread> maybe_restore_thread
+	= maybe_switch_inferior (m_inf_for_target_calls);
+
       target_fetch_registers (this, regnum);
 
       /* A number of targets can't access the whole set of raw
@@ -842,6 +850,9 @@ regcache::raw_write (int regnum, const gdb_byte *buf)
 		  m_descr->sizeof_register[regnum]) == 0))
     return;
 
+  gdb::optional<scoped_restore_current_thread> maybe_restore_thread
+    = maybe_switch_inferior (m_inf_for_target_calls);
+
   target_prepare_to_store (this);
   raw_supply (regnum, buf);
 
@@ -1610,16 +1621,16 @@ regcache_count (process_stratum_target *target, ptid_t ptid)
 /* Wrapper around get_thread_arch_aspace_regcache that does some self checks.  */
 
 static void
-get_thread_arch_aspace_regcache_and_check (process_stratum_target *target,
+get_thread_arch_aspace_regcache_and_check (inferior *inf_for_target_calls,
 					   ptid_t ptid)
 {
   /* We currently only test with a single gdbarch.  Any gdbarch will do, so use
      the current inferior's gdbarch.  Also use the current inferior's address
      space.  */
-  gdbarch *arch = current_inferior ()->gdbarch;
-  address_space *aspace = current_inferior ()->aspace;
-  regcache *regcache
-    = get_thread_arch_aspace_regcache (target, ptid, arch, aspace);
+  gdbarch *arch = inf_for_target_calls->gdbarch;
+  address_space *aspace = inf_for_target_calls->aspace;
+  regcache *regcache = get_thread_arch_aspace_regcache (inf_for_target_calls,
+							ptid, arch, aspace);
 
   SELF_CHECK (regcache != NULL);
   SELF_CHECK (regcache->ptid () == ptid);
@@ -1633,6 +1644,9 @@ get_thread_arch_aspace_regcache_and_check (process_stratum_target *target,
 struct regcache_test_data
 {
   regcache_test_data ()
+      /* The specific arch doesn't matter.  */
+    : test_ctx_1 (current_inferior ()->gdbarch),
+      test_ctx_2 (current_inferior ()->gdbarch)
   {
     /* Ensure the regcaches container is empty at the start.  */
     registers_changed ();
@@ -1644,8 +1658,8 @@ struct regcache_test_data
     registers_changed ();
   }
 
-  test_target_ops test_target1;
-  test_target_ops test_target2;
+  scoped_mock_context<test_target_ops> test_ctx_1;
+  scoped_mock_context<test_target_ops> test_ctx_2;
 };
 
 using regcache_test_data_up = std::unique_ptr<regcache_test_data>;
@@ -1670,12 +1684,12 @@ populate_regcaches_for_test ()
       for (long lwp : { 1, 2, 3 })
 	{
 	  get_thread_arch_aspace_regcache_and_check
-	    (&data->test_target1, ptid_t (pid, lwp));
+	    (&data->test_ctx_1.mock_inferior, ptid_t (pid, lwp));
 	  expected_regcache_size++;
 	  SELF_CHECK (regcaches_size () == expected_regcache_size);
 
 	  get_thread_arch_aspace_regcache_and_check
-	    (&data->test_target2, ptid_t (pid, lwp));
+	    (&data->test_ctx_2.mock_inferior, ptid_t (pid, lwp));
 	  expected_regcache_size++;
 	  SELF_CHECK (regcaches_size () == expected_regcache_size);
 	}
@@ -1693,7 +1707,8 @@ get_thread_arch_aspace_regcache_test ()
   size_t regcaches_size_before = regcaches_size ();
 
   /* Test that getting an existing regcache doesn't create a new one.  */
-  get_thread_arch_aspace_regcache_and_check (&data->test_target1, ptid_t (2, 2));
+  get_thread_arch_aspace_regcache_and_check (&data->test_ctx_1.mock_inferior,
+					     ptid_t (2, 2));
   SELF_CHECK (regcaches_size () == regcaches_size_before);
 }
 
@@ -1715,12 +1730,14 @@ registers_changed_ptid_target_test ()
 {
   regcache_test_data_up data = populate_regcaches_for_test ();
 
-  registers_changed_ptid (&data->test_target1, minus_one_ptid);
+  registers_changed_ptid (&data->test_ctx_1.mock_target, minus_one_ptid);
   SELF_CHECK (regcaches_size () == 6);
 
   /* Check that we deleted the regcache for the right target.  */
-  SELF_CHECK (regcache_count (&data->test_target1, ptid_t (2, 2)) == 0);
-  SELF_CHECK (regcache_count (&data->test_target2, ptid_t (2, 2)) == 1);
+  SELF_CHECK (regcache_count (&data->test_ctx_1.mock_target,
+			      ptid_t (2, 2)) == 0);
+  SELF_CHECK (regcache_count (&data->test_ctx_2.mock_target,
+			      ptid_t (2, 2)) == 1);
 }
 
 /* Test marking regcaches of a specific (target, pid) as changed.  */
@@ -1730,13 +1747,15 @@ registers_changed_ptid_target_pid_test ()
 {
   regcache_test_data_up data = populate_regcaches_for_test ();
 
-  registers_changed_ptid (&data->test_target1, ptid_t (2));
+  registers_changed_ptid (&data->test_ctx_1.mock_target, ptid_t (2));
   SELF_CHECK (regcaches_size () == 9);
 
   /* Regcaches from target1 should not exist, while regcaches from target2
      should exist.  */
-  SELF_CHECK (regcache_count (&data->test_target1, ptid_t (2, 2)) == 0);
-  SELF_CHECK (regcache_count (&data->test_target2, ptid_t (2, 2)) == 1);
+  SELF_CHECK (regcache_count (&data->test_ctx_1.mock_target,
+			      ptid_t (2, 2)) == 0);
+  SELF_CHECK (regcache_count (&data->test_ctx_2.mock_target,
+			      ptid_t (2, 2)) == 1);
 }
 
 /* Test marking regcaches of a specific (target, ptid) as changed.  */
@@ -1746,12 +1765,14 @@ registers_changed_ptid_target_ptid_test ()
 {
   regcache_test_data_up data = populate_regcaches_for_test ();
 
-  registers_changed_ptid (&data->test_target1, ptid_t (2, 2));
+  registers_changed_ptid (&data->test_ctx_1.mock_target, ptid_t (2, 2));
   SELF_CHECK (regcaches_size () == 11);
 
   /* Check that we deleted the regcache for the right target.  */
-  SELF_CHECK (regcache_count (&data->test_target1, ptid_t (2, 2)) == 0);
-  SELF_CHECK (regcache_count (&data->test_target2, ptid_t (2, 2)) == 1);
+  SELF_CHECK (regcache_count (&data->test_ctx_1.mock_target,
+			      ptid_t (2, 2)) == 0);
+  SELF_CHECK (regcache_count (&data->test_ctx_2.mock_target,
+			      ptid_t (2, 2)) == 1);
 }
 
 class target_ops_no_register : public test_target_ops
@@ -1812,9 +1833,9 @@ target_ops_no_register::xfer_partial (enum target_object object,
 class readwrite_regcache : public regcache
 {
 public:
-  readwrite_regcache (process_stratum_target *target,
+  readwrite_regcache (inferior *inf_for_target_calls,
 		      struct gdbarch *gdbarch)
-    : regcache (target, gdbarch, nullptr)
+    : regcache (inf_for_target_calls, gdbarch, nullptr)
   {}
 };
 
@@ -1861,7 +1882,8 @@ cooked_read_test (struct gdbarch *gdbarch)
 	break;
     }
 
-  readwrite_regcache readwrite (&mockctx.mock_target, gdbarch);
+  readwrite_regcache readwrite (&mockctx.mock_inferior, gdbarch);
+  readwrite.set_ptid (mockctx.mock_ptid);
   gdb::def_vector<gdb_byte> buf (register_size (gdbarch, nonzero_regnum));
 
   readwrite.raw_read (nonzero_regnum, buf.data ());
@@ -1978,7 +2000,8 @@ cooked_write_test (struct gdbarch *gdbarch)
 
   /* Create a mock environment.  A process_stratum target pushed.  */
   scoped_mock_context<target_ops_no_register> ctx (gdbarch);
-  readwrite_regcache readwrite (&ctx.mock_target, gdbarch);
+  readwrite_regcache readwrite (&ctx.mock_inferior, gdbarch);
+  readwrite.set_ptid (ctx.mock_ptid);
   const int num_regs = gdbarch_num_cooked_regs (gdbarch);
 
   for (auto regnum = 0; regnum < num_regs; regnum++)
@@ -2093,9 +2116,9 @@ regcache_thread_ptid_changed ()
   gdb_assert (regcaches.empty ());
 
   /* Populate the regcaches container.  */
-  get_thread_arch_aspace_regcache (&target1.mock_target, old_ptid, arch,
+  get_thread_arch_aspace_regcache (&target1.mock_inferior, old_ptid, arch,
 				   nullptr);
-  get_thread_arch_aspace_regcache (&target2.mock_target, old_ptid, arch,
+  get_thread_arch_aspace_regcache (&target2.mock_inferior, old_ptid, arch,
 				   nullptr);
 
   gdb_assert (regcaches.size () == 2);
diff --git a/gdb/regcache.h b/gdb/regcache.h
index 2bd2f57b8332..57ddac465f09 100644
--- a/gdb/regcache.h
+++ b/gdb/regcache.h
@@ -29,6 +29,7 @@ struct gdbarch;
 struct address_space;
 class thread_info;
 struct process_stratum_target;
+struct inferior;
 
 extern struct regcache *get_current_regcache (void);
 extern struct regcache *get_thread_regcache (process_stratum_target *target,
@@ -40,7 +41,7 @@ extern struct regcache *get_thread_regcache (thread_info *thread);
 extern struct regcache *get_thread_arch_regcache
   (process_stratum_target *targ, ptid_t, struct gdbarch *);
 extern struct regcache *get_thread_arch_aspace_regcache
-  (process_stratum_target *target, ptid_t,
+  (inferior *inf_for_target_calls, ptid_t,
    struct gdbarch *, struct address_space *);
 
 extern enum register_status
@@ -421,7 +422,7 @@ class regcache : public detached_regcache
   void debug_print_register (const char *func, int regno);
 
 protected:
-  regcache (process_stratum_target *target, gdbarch *gdbarch,
+  regcache (inferior *inf_for_target_calls, gdbarch *gdbarch,
 	    const address_space *aspace);
 
 private:
@@ -448,13 +449,21 @@ class regcache : public detached_regcache
      makes sense, like PC or SP).  */
   const address_space * const m_aspace;
 
+  /* The inferior to switch to, to make target calls.
+
+     This may not be the inferior of thread M_PTID.  For instance, this
+     regcache might be for a fork child we are about to detach, so there will
+     never be an inferior for that thread / process.  Nevertheless, we need to
+     be able to switch to the target stack that can handle register reads /
+     writes for this regcache, and that's what this inferior is for.  */
+  inferior *m_inf_for_target_calls;
+
   /* If this is a read-write cache, which thread's registers is
      it connected to?  */
-  process_stratum_target *m_target;
   ptid_t m_ptid;
 
   friend struct regcache *
-  get_thread_arch_aspace_regcache (process_stratum_target *target, ptid_t ptid,
+  get_thread_arch_aspace_regcache (inferior *inf_for_target_calls, ptid_t ptid,
 				   struct gdbarch *gdbarch,
 				   struct address_space *aspace);
 };
-- 
2.40.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 6/7] gdb: switch to right inferior in fetch_inferior_event
  2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
                   ` (4 preceding siblings ...)
  2023-04-03 18:52 ` [PATCH 5/7] gdb: make regcache::raw_update switch to right inferior Simon Marchi
@ 2023-04-03 18:52 ` Simon Marchi
  2023-04-03 18:52 ` [PATCH 7/7] gdb/amdgpu: add follow fork and exec support Simon Marchi
  2023-04-13 15:54 ` [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
  7 siblings, 0 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-03 18:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi, Pedro Alves

The problem explained and fixed in the previous patch could have also
been fixed by this patch.  But I think it's good change anyhow, that
could prevent future bugs, so here it is.

fetch_inferior_event switches to an arbitrary (in practice, the first) inferior
of the process target of the inferior used to fetch the event.  The idea is
that the event handling code will need to do some target calls, so we want to
switch to an inferior that has target target.

However, you can have two inferiors that share a process target, but with one
inferior having an additional target on top:

        inf 1            inf 2
        -----            -----
                         another target
        process target   process target
        exec             exec

Let's say inferior 2 is selected by do_target_wait and returns an event that is
really synthetized by "another target".  This "another target" could be a
thread or record stratum target (in the case explained by the previous patch,
it was the arch stratum target, but it's because the amd-dbgapi abuses the arch
layer).  fetch_inferior_event will then switch to the first inferior with
"process target", so inferior 1.  handle_signal_stop then tries to fetch the
thread's registers:

    ecs->event_thread->set_stop_pc
      (regcache_read_pc (get_thread_regcache (ecs->event_thread)));

This will try to get the thread's register by calling into the current target
stack, the stack of inferior 1.  This is problematic because "another target"
might have a special fetch_registers implementation.

I think it would be a good idea to switch to the inferior for which the
even was reported, not just some inferior of the same process target.
This will ensure that any target call done before we eventually call
context_switch will be done on the full target stack that reported the
event.

Not all events are associated to an inferior though.  For instance,
TARGET_WAITKIND_NO_RESUMED.  In those cases, some targets return
null_ptid, some return minus_one_ptid (ideally the expected return value
should be clearly defined / documented).  So, if the ptid returned is
either of these, switch to an arbitrary inferior with that process
target, as before.

Change-Id: I1ffc8c1095125ab591d0dc79ea40025b1d7454af
Reviewed-By: Pedro Alves <pedro@palves.net>
---
 gdb/infrun.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index f32e037f3649..851c01f66130 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4358,9 +4358,13 @@ fetch_inferior_event ()
 
     gdb_assert (ecs.ws.kind () != TARGET_WAITKIND_IGNORE);
 
-    /* Switch to the target that generated the event, so we can do
-       target calls.  */
-    switch_to_target_no_thread (ecs.target);
+    /* Switch to the inferior that generated the event, so we can do
+       target calls.  If the event was not associated to a ptid,  */
+    if (ecs.ptid != null_ptid
+	&& ecs.ptid != minus_one_ptid)
+      switch_to_inferior_no_thread (find_inferior_ptid (ecs.target, ecs.ptid));
+    else
+      switch_to_target_no_thread (ecs.target);
 
     if (debug_infrun)
       print_target_wait_results (minus_one_ptid, ecs.ptid, ecs.ws);
-- 
2.40.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 7/7] gdb/amdgpu: add follow fork and exec support
  2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
                   ` (5 preceding siblings ...)
  2023-04-03 18:52 ` [PATCH 6/7] gdb: switch to right inferior in fetch_inferior_event Simon Marchi
@ 2023-04-03 18:52 ` Simon Marchi
  2023-04-13 15:54 ` [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
  7 siblings, 0 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-03 18:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi, Pedro Alves

Prior to this patch, it's not possible for GDB to debug GPU code in fork
children or after an exec.  The amd-dbgapi target attaches to processes
when an inferior appears due to a "run" or "attach" command, but not
after a fork or exec.  This patch adds support for that, such that it's
possible to for an inferior to fork and for GDB to debug the GPU code in
the child.

To achieve that, use the inferior_forked and inferior_execd observers.

In the case of fork, we have nothing to do if `child_inf` is nullptr,
meaning that GDB won't debug the child.  We also don't attach if the
inferior has vforked.  We are already attached to the parent's address
space, which is shared with the child, so trying to attach would cause
problems.  And anyway, the inferior can't do anything other than exec or
exit, it certainly won't start GPU kernels before exec'ing.

In the case of exec, we detach from the exec'ing inferior and attach to
the following inferior.  This works regardless of whether they are the
same or not.  If they are the same, meaning the execution continues in
the existing inferior, we need to do a detach/attach anyway, as
amd-dbgapi needs to be aware of the new address space created by the
exec.

Note that we use observers and not target_ops::follow_{fork,exec} here.
When the amd-dbgapi target is compiled in, it will attach (in the
amd_dbgapi_process_attach sense, not the ptrace sense) to native
inferiors when they appear, but won't push itself on the inferior's
target stack just yet.  It only pushes itself if the inferior
initializes the ROCm runtime.  So, if a non-GPU-using inferior calls
fork, an amd_dbgapi_target::follow_fork method would not get called.
Same for exec.  A previous version of the code had the amd-dbgapi target
pushed all the time, in which case we could use the target methods.  But
we prefer having the target pushed only when necessary, it's less
intrusive when doing native debugging that doesn't involve the GPU.

Change-Id: I5819c151c371120da8bab2fa9cbfa8769ba1d6f9
Reviewed-By: Pedro Alves <pedro@palves.net>
---
 gdb/amd-dbgapi-target.c                       | 39 ++++++++
 .../fork-exec-gpu-to-non-gpu-execee.cpp       | 27 ++++++
 .../fork-exec-gpu-to-non-gpu-execer.cpp       | 55 ++++++++++++
 .../gdb.rocm/fork-exec-gpu-to-non-gpu.exp     | 89 +++++++++++++++++++
 .../fork-exec-non-gpu-to-gpu-execee.cpp       | 36 ++++++++
 .../fork-exec-non-gpu-to-gpu-execer.cpp       | 46 ++++++++++
 .../gdb.rocm/fork-exec-non-gpu-to-gpu.exp     | 88 ++++++++++++++++++
 7 files changed, 380 insertions(+)
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execee.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execer.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu.exp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execee.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execer.cpp
 create mode 100644 gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu.exp

diff --git a/gdb/amd-dbgapi-target.c b/gdb/amd-dbgapi-target.c
index f5161038c51d..61b9805abca0 100644
--- a/gdb/amd-dbgapi-target.c
+++ b/gdb/amd-dbgapi-target.c
@@ -1343,6 +1343,17 @@ attach_amd_dbgapi (inferior *inf)
       return;
     }
 
+  /* dbgapi can't attach to a vfork child (a process born from a vfork that
+     hasn't exec'ed yet) while we are still attached to the parent.  It would
+     not be useful for us to attach to vfork children anyway, because vfork
+     children are very restricted in what they can do (see vfork(2)) and aren't
+     going to launch some GPU programs that we need to debug.  To avoid this
+     problem, we don't push the amd-dbgapi target / attach dbgapi in vfork
+     children.  If a vfork child execs, we'll try enabling the amd-dbgapi target
+     through the inferior_execd observer.  */
+  if (inf->vfork_parent != nullptr)
+    return;
+
   auto *info = get_amd_dbgapi_inferior_info (inf);
 
   /* Are we already attached?  */
@@ -1655,6 +1666,32 @@ amd_dbgapi_target_inferior_created (inferior *inf)
   attach_amd_dbgapi (inf);
 }
 
+/* inferior_execd observer.  */
+
+static void
+amd_dbgapi_inferior_execd (inferior *exec_inf, inferior *follow_inf)
+{
+  /* The inferior has EXEC'd and the process image has changed.  The dbgapi is
+     attached to the old process image, so we need to detach and re-attach to
+     the new process image.  */
+  detach_amd_dbgapi (exec_inf);
+  attach_amd_dbgapi (follow_inf);
+}
+
+/* inferior_forked observer.  */
+
+static void
+amd_dbgapi_inferior_forked (inferior *parent_inf, inferior *child_inf,
+			    target_waitkind fork_kind)
+{
+  if (child_inf != nullptr  && fork_kind != TARGET_WAITKIND_VFORKED)
+    {
+      scoped_restore_current_thread restore_thread;
+      switch_to_thread (*child_inf->threads ().begin ());
+      attach_amd_dbgapi (child_inf);
+    }
+}
+
 /* inferior_exit observer.
 
    This covers normal exits, but also detached inferiors (including detached
@@ -1924,6 +1961,8 @@ _initialize_amd_dbgapi_target ()
   gdb::observers::inferior_created.attach
     (amd_dbgapi_target_inferior_created,
      amd_dbgapi_target_inferior_created_observer_token, "amd-dbgapi");
+  gdb::observers::inferior_execd.attach (amd_dbgapi_inferior_execd, "amd-dbgapi");
+  gdb::observers::inferior_forked.attach (amd_dbgapi_inferior_forked, "amd-dbgapi");
   gdb::observers::inferior_exit.attach (amd_dbgapi_inferior_exited, "amd-dbgapi");
   gdb::observers::inferior_pre_detach.attach (amd_dbgapi_inferior_pre_detach, "amd-dbgapi");
 
diff --git a/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execee.cpp b/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execee.cpp
new file mode 100644
index 000000000000..eacfcd86faa8
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execee.cpp
@@ -0,0 +1,27 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2023 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+static void
+break_here_execee (void)
+{}
+
+int
+main (void)
+{
+  break_here_execee ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execer.cpp b/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execer.cpp
new file mode 100644
index 000000000000..1a731aeca517
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu-execer.cpp
@@ -0,0 +1,55 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2023 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <hip/hip_runtime.h>
+#include <unistd.h>
+
+__global__ static void
+kernel1 ()
+{}
+
+__device__ static void
+break_here_execer ()
+{
+}
+
+__global__ static void
+kernel2 ()
+{
+  break_here_execer ();
+}
+
+int
+main ()
+{
+  /* Launch a first kernel to make sure the runtime is active by the time we
+     call fork.  */
+  kernel1<<<1, 1>>> ();
+
+  /* fork + exec while the runtime is active.  */
+  if (FORK () == 0)
+    {
+      int ret = execl (EXECEE, EXECEE, NULL);
+      perror ("exec");
+      abort ();
+    }
+
+  kernel2<<<1, 1>>> ();
+
+  hipDeviceSynchronize ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu.exp b/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu.exp
new file mode 100644
index 000000000000..852294b7067b
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/fork-exec-gpu-to-non-gpu.exp
@@ -0,0 +1,89 @@
+# Copyright 2021-2023 Free Software Foundation, Inc.
+
+# This file is part of GDB.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Verify handling of a GPU program that does a (v)fork + exec to execute
+# a non-GPU program.
+
+load_lib rocm.exp
+
+require allow_hipcc_tests
+
+standard_testfile -execer.cpp -execee.cpp
+
+set srcfile_execer "$srcfile"
+set srcfile_execee "$srcfile2"
+set binfile_execee "$binfile-execee"
+
+# Compile two versions of execer, one that uses fork and one that uses vfork.
+foreach_with_prefix fork_func { fork vfork } {
+    set opts [list debug hip additional_flags=-DFORK=$fork_func \
+	additional_flags=-DEXECEE="${::binfile_execee}"]
+    if {[build_executable "failed to prepare" ${::binfile}-execer-${fork_func} \
+	    $srcfile_execer $opts]} {
+	return
+    }
+}
+
+if {[build_executable "failed to prepare" $binfile_execee $srcfile_execee \
+	{debug}]} {
+    return
+}
+
+proc do_test { detach-on-fork follow-fork-mode fork_func } {
+    # In this case, the parent can't execute, as it's blocked in
+    # vfork.  Skip it.
+    if { ${detach-on-fork} == "off"
+	 && ${follow-fork-mode} == "parent"
+	 && ${fork_func} == "vfork" } {
+	return
+    }
+
+    with_rocm_gpu_lock {
+	clean_restart ${::binfile}-execer-${fork_func}
+
+	gdb_test_no_output "set detach-on-fork ${detach-on-fork}"
+	gdb_test_no_output "set follow-fork-mode ${follow-fork-mode}"
+
+	if { ${follow-fork-mode} == "parent" } {
+	    runto break_here_execer allow-pending message
+	    gdb_continue_to_end "continue parent to end" "continue" 1
+
+	    if { ${detach-on-fork} == "off" } {
+		gdb_test "inferior 2" "Switching to inferior 2 .*"
+		gdb_continue_to_end "continue child to end" "continue" 1
+	    }
+	} elseif { ${follow-fork-mode} == "child" } {
+	    runto break_here_execee allow-pending message
+	    gdb_continue_to_end "continue child to end" "continue" 1
+
+	    if { ${detach-on-fork} == "off" } {
+		gdb_test "inferior 1" "Switching to inferior 1 .*"
+		gdb_continue_to_end "continue parent to end" "continue" 1
+	    }
+	} else {
+	    error "unexpected follow-fork-mode value: ${follow-fork-mode}"
+	}
+    }
+}
+
+foreach_with_prefix detach-on-fork { on off } {
+    foreach_with_prefix follow-fork-mode { parent child } {
+	foreach_with_prefix fork_func { fork vfork } {
+	    do_test ${detach-on-fork} ${follow-fork-mode} $fork_func
+	}
+    }
+}
diff --git a/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execee.cpp b/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execee.cpp
new file mode 100644
index 000000000000..2de8fe20a0d6
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execee.cpp
@@ -0,0 +1,36 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2023 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <hip/hip_runtime.h>
+
+__device__ static void
+break_here_execee ()
+{}
+
+__global__ void
+kernel ()
+{
+  break_here_execee ();
+}
+
+int
+main ()
+{
+  kernel<<<1, 1>>> ();
+  hipDeviceSynchronize ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execer.cpp b/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execer.cpp
new file mode 100644
index 000000000000..3ee07949273a
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu-execer.cpp
@@ -0,0 +1,46 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2023 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static void
+break_here_execer ()
+{}
+
+int
+main ()
+{
+  /* FORK is defined to fork or vfork by the test.  */
+  int pid = FORK ();
+  if (pid != 0)
+    {
+      /* Parent.  */
+      break_here_execer ();
+    }
+  else
+    {
+      /* EXECEE is defined by the test.  */
+      int ret = execl (EXECEE, EXECEE, NULL);
+      perror ("exec");
+      abort ();
+    }
+
+    return 0;
+}
diff --git a/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu.exp b/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu.exp
new file mode 100644
index 000000000000..e372db5a32e6
--- /dev/null
+++ b/gdb/testsuite/gdb.rocm/fork-exec-non-gpu-to-gpu.exp
@@ -0,0 +1,88 @@
+# Copyright 2021-2023 Free Software Foundation, Inc.
+
+# This file is part of GDB.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Verify that we can debug a GPU program in a child after a (v)fork + exec.
+
+load_lib rocm.exp
+
+require allow_hipcc_tests
+
+standard_testfile -execer.cpp -execee.cpp
+
+set srcfile_execer "$srcfile"
+set srcfile_execee "$srcfile2"
+set binfile_execee "$binfile-execee"
+
+# Compile two versions of execer, one that uses fork and one that uses vfork.
+foreach_with_prefix fork_func { fork vfork } {
+    set opts [list additional_flags=-DFORK=$fork_func \
+	additional_flags=-DEXECEE="${::binfile_execee}"]
+    if {[build_executable "failed to prepare" ${::binfile}-execer-${fork_func} \
+	    $srcfile_execer $opts]} {
+	return
+    }
+}
+
+if {[build_executable "failed to prepare" $binfile_execee $srcfile_execee \
+	{debug hip}]} {
+    return
+}
+
+proc do_test { detach-on-fork follow-fork-mode fork_func } {
+    # In this case, the parent can't execute, as it's blocked in
+    # vfork.  Skip it.
+    if { ${detach-on-fork} == "off"
+	 && ${follow-fork-mode} == "parent"
+	 && ${fork_func} == "vfork" } {
+	return
+    }
+
+    with_rocm_gpu_lock {
+	clean_restart ${::binfile}-execer-${fork_func}
+
+	gdb_test_no_output "set detach-on-fork ${detach-on-fork}"
+	gdb_test_no_output "set follow-fork-mode ${follow-fork-mode}"
+
+	if { ${follow-fork-mode} == "parent" } {
+	    runto break_here_execer allow-pending message
+	    gdb_continue_to_end "continue parent to end" "continue" 1
+
+	    if { ${detach-on-fork} == "off" } {
+		gdb_test "inferior 2" "Switching to inferior 2 .*"
+		gdb_continue_to_end "continue child to end" "continue" 1
+	    }
+	} elseif { ${follow-fork-mode} == "child" } {
+	    runto break_here_execee allow-pending message
+	    gdb_continue_to_end "continue child to end" "continue" 1
+
+	    if { ${detach-on-fork} == "off" } {
+		gdb_test "inferior 1" "Switching to inferior 1 .*"
+		gdb_continue_to_end "continue parent to end" "continue" 1
+	    }
+	} else {
+	    error "unexpected follow-fork-mode value: ${follow-fork-mode}"
+	}
+    }
+}
+
+foreach_with_prefix detach-on-fork { on off } {
+    foreach_with_prefix follow-fork-mode { parent child } {
+	foreach_with_prefix fork_func { fork vfork } {
+	    do_test ${detach-on-fork} ${follow-fork-mode} $fork_func
+	}
+    }
+}
-- 
2.40.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 0/7] amdgpu: handle fork and exec
  2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
                   ` (6 preceding siblings ...)
  2023-04-03 18:52 ` [PATCH 7/7] gdb/amdgpu: add follow fork and exec support Simon Marchi
@ 2023-04-13 15:54 ` Simon Marchi
  2023-04-17 17:57   ` Simon Marchi
  7 siblings, 1 reply; 10+ messages in thread
From: Simon Marchi @ 2023-04-13 15:54 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 4/3/23 14:52, Simon Marchi via Gdb-patches wrote:
> This series adds support for fork and exec to the amdgpu port.  This
> means making sure that the appropriate cleanups are done when an
> inferior using the GPU forks and / or execs, and the appropriates
> actions taken so that we can properly debug an inferior using the GPU
> post-fork or post-exec.

If there is no feedback on this (the non-amdgpu-specific bits may be
relevant to review), I intend to push this next week.

Simon

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 0/7] amdgpu: handle fork and exec
  2023-04-13 15:54 ` [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
@ 2023-04-17 17:57   ` Simon Marchi
  0 siblings, 0 replies; 10+ messages in thread
From: Simon Marchi @ 2023-04-17 17:57 UTC (permalink / raw)
  To: Simon Marchi, Simon Marchi, gdb-patches

On 4/13/23 11:54, Simon Marchi wrote:
> On 4/3/23 14:52, Simon Marchi via Gdb-patches wrote:
>> This series adds support for fork and exec to the amdgpu port.  This
>> means making sure that the appropriate cleanups are done when an
>> inferior using the GPU forks and / or execs, and the appropriates
>> actions taken so that we can properly debug an inferior using the GPU
>> post-fork or post-exec.
> 
> If there is no feedback on this (the non-amdgpu-specific bits may be
> relevant to review), I intend to push this next week.
> 
> Simon

I just pushed this.

Simon

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2023-04-17 17:57 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-03 18:52 [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
2023-04-03 18:52 ` [PATCH 1/7] gdb: pass execing and following inferior to inferior_execd observers Simon Marchi
2023-04-03 18:52 ` [PATCH 2/7] gdb: add inferior_forked observable Simon Marchi
2023-04-03 18:52 ` [PATCH 3/7] gdb: remove regcache::target Simon Marchi
2023-04-03 18:52 ` [PATCH 4/7] gdb: add maybe_switch_inferior function Simon Marchi
2023-04-03 18:52 ` [PATCH 5/7] gdb: make regcache::raw_update switch to right inferior Simon Marchi
2023-04-03 18:52 ` [PATCH 6/7] gdb: switch to right inferior in fetch_inferior_event Simon Marchi
2023-04-03 18:52 ` [PATCH 7/7] gdb/amdgpu: add follow fork and exec support Simon Marchi
2023-04-13 15:54 ` [PATCH 0/7] amdgpu: handle fork and exec Simon Marchi
2023-04-17 17:57   ` Simon Marchi

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 read-only IMAP folder(s) and NNTP newsgroup(s).