Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH v3 0/2] Support for Haiku/x86-64 in GDB
@ 2026-04-08 16:32 Jérôme Duval
  2026-04-08 16:32 ` [PATCH v3 1/2] gdbserver: Haiku support Jérôme Duval
  2026-04-08 16:32 ` [PATCH v3 2/2] gdb: " Jérôme Duval
  0 siblings, 2 replies; 4+ messages in thread
From: Jérôme Duval @ 2026-04-08 16:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: me, Jérôme Duval

This series adds support for Haiku/x86-64 to GDB.

Initial support was done by Trung Nguyen for GDB 15.1 for GSoC 2024:
See blog entries https://www.haiku-os.org/tags/gdb
Original Port repository: https://github.com/trungnt2910/gdb-haiku

I mostly adapted to the next major releases.
These patches are maintained at HaikuPorts, we would like to upstream them.

Changes in v3:
- Whitespace/tabs, struct keyword
- perror_with_name i18n friendly
- Haiku/i386 removed
- Rebase on current

Jérôme Duval (2):
  gdbserver: Haiku support
  gdb: Haiku support

 gdb/Makefile.in              |   25 +
 gdb/NEWS                     |    2 +
 gdb/amd64-haiku-nat.c        |  151 ++
 gdb/amd64-haiku-tdep.c       |  142 ++
 gdb/configure                |    2 +-
 gdb/configure.host           |    2 +
 gdb/configure.nat            |   15 +
 gdb/configure.tgt            |    6 +
 gdb/haiku-nat.c              |  776 ++++++++++
 gdb/haiku-nat.h              |   75 +
 gdb/haiku-tdep.c             |  194 +++
 gdb/haiku-tdep.h             |   44 +
 gdb/nat/haiku-debug.c        |   43 +
 gdb/nat/haiku-nat.c          | 2808 ++++++++++++++++++++++++++++++++++
 gdb/nat/haiku-nat.h          |  429 ++++++
 gdb/nat/haiku-nub-message.c  |   50 +
 gdb/nat/haiku-nub-message.h  |  141 ++
 gdb/nat/haiku-osdata.c       |  445 ++++++
 gdb/nat/haiku-osdata.h       |   26 +
 gdb/solib-haiku.c            |  118 ++
 gdb/solib-haiku.h            |   29 +
 gdbserver/Makefile.in        |    6 +
 gdbserver/configure          |    2 +-
 gdbserver/configure.srv      |    9 +
 gdbserver/haiku-amd64-low.cc |  262 ++++
 gdbserver/haiku-low.cc       |  613 ++++++++
 gdbserver/haiku-low.h        |  100 ++
 gdbserver/remote-utils.cc    |    4 +
 gdbsupport/osabi.def         |    1 +
 gdbsupport/signals.cc        |   10 +
 include/gdb/signals.def      |    4 +-
 31 files changed, 6531 insertions(+), 3 deletions(-)
 create mode 100644 gdb/amd64-haiku-nat.c
 create mode 100644 gdb/amd64-haiku-tdep.c
 create mode 100644 gdb/haiku-nat.c
 create mode 100644 gdb/haiku-nat.h
 create mode 100644 gdb/haiku-tdep.c
 create mode 100644 gdb/haiku-tdep.h
 create mode 100644 gdb/nat/haiku-debug.c
 create mode 100644 gdb/nat/haiku-nat.c
 create mode 100644 gdb/nat/haiku-nat.h
 create mode 100644 gdb/nat/haiku-nub-message.c
 create mode 100644 gdb/nat/haiku-nub-message.h
 create mode 100644 gdb/nat/haiku-osdata.c
 create mode 100644 gdb/nat/haiku-osdata.h
 create mode 100644 gdb/solib-haiku.c
 create mode 100644 gdb/solib-haiku.h
 create mode 100644 gdbserver/haiku-amd64-low.cc
 create mode 100644 gdbserver/haiku-low.cc
 create mode 100644 gdbserver/haiku-low.h

-- 
2.43.0


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

* [PATCH v3 1/2] gdbserver: Haiku support
  2026-04-08 16:32 [PATCH v3 0/2] Support for Haiku/x86-64 in GDB Jérôme Duval
@ 2026-04-08 16:32 ` Jérôme Duval
  2026-04-08 16:32 ` [PATCH v3 2/2] gdb: " Jérôme Duval
  1 sibling, 0 replies; 4+ messages in thread
From: Jérôme Duval @ 2026-04-08 16:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: me, Jérôme Duval

Initial support was done by Trung Nguyen for GDB 15.1 for GSoC 2024:
See blog entries https://www.haiku-os.org/tags/gdb
Original Port repository: https://github.com/trungnt2910/gdb-haiku

I mostly adapted to the next major releases.
---
 gdb/nat/haiku-debug.c        |   43 +
 gdb/nat/haiku-nat.c          | 2808 ++++++++++++++++++++++++++++++++++
 gdb/nat/haiku-nat.h          |  429 ++++++
 gdb/nat/haiku-nub-message.c  |   50 +
 gdb/nat/haiku-nub-message.h  |  141 ++
 gdb/nat/haiku-osdata.c       |  445 ++++++
 gdb/nat/haiku-osdata.h       |   26 +
 gdbserver/Makefile.in        |    6 +
 gdbserver/configure          |    2 +-
 gdbserver/configure.srv      |    9 +
 gdbserver/haiku-amd64-low.cc |  262 ++++
 gdbserver/haiku-low.cc       |  613 ++++++++
 gdbserver/haiku-low.h        |  100 ++
 gdbserver/remote-utils.cc    |    4 +
 gdbsupport/signals.cc        |   10 +
 include/gdb/signals.def      |    4 +-
 16 files changed, 4950 insertions(+), 2 deletions(-)
 create mode 100644 gdb/nat/haiku-debug.c
 create mode 100644 gdb/nat/haiku-nat.c
 create mode 100644 gdb/nat/haiku-nat.h
 create mode 100644 gdb/nat/haiku-nub-message.c
 create mode 100644 gdb/nat/haiku-nub-message.h
 create mode 100644 gdb/nat/haiku-osdata.c
 create mode 100644 gdb/nat/haiku-osdata.h
 create mode 100644 gdbserver/haiku-amd64-low.cc
 create mode 100644 gdbserver/haiku-low.cc
 create mode 100644 gdbserver/haiku-low.h

diff --git a/gdb/nat/haiku-debug.c b/gdb/nat/haiku-debug.c
new file mode 100644
index 00000000..9d983232
--- /dev/null
+++ b/gdb/nat/haiku-debug.c
@@ -0,0 +1,43 @@
+/* Haiku re-exports for debugging functions with conflicting names.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "gdbsupport/common-defs.h"
+
+extern decltype (debug_printf) haiku_debug_printf;
+extern decltype (debug_vprintf) haiku_debug_vprintf;
+
+/* Re-export of debug_printf.  */
+
+void
+haiku_debug_printf (const char *format, ...)
+{
+  va_list ap;
+
+  va_start (ap, format);
+  debug_vprintf (format, ap);
+  va_end (ap);
+}
+
+/* Re-export of debug_vprintf.  */
+
+void
+haiku_debug_vprintf (const char *format, va_list ap)
+{
+  debug_vprintf (format, ap);
+}
diff --git a/gdb/nat/haiku-nat.c b/gdb/nat/haiku-nat.c
new file mode 100644
index 00000000..6395be43
--- /dev/null
+++ b/gdb/nat/haiku-nat.c
@@ -0,0 +1,2808 @@
+/* Internal interfaces for the Haiku code.
+
+   Copyright (C) 2026 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/>.  */
+
+/* Wrap this to prevent name clashes with a similarly named function in
+   Haiku's system headers.  */
+#define debug_printf haiku_debug_printf
+#define debug_vprintf haiku_debug_vprintf
+
+#include "gdbsupport/common-defs.h"
+#include "gdbsupport/event-pipe.h"
+#include "gdbsupport/gdb_signals.h"
+
+#include "diagnostics.h"
+#include "target/waitstatus.h"
+
+#undef debug_printf
+#undef debug_vprintf
+/* Now we can safely include Haiku headers.  */
+
+#include "nat/haiku-nat.h"
+#include "nat/haiku-nub-message.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <set>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <debugger.h>
+#include <elf.h>
+
+#define RETURN_IF_FAIL(exp)						   \
+  do									  \
+    {									 \
+      status_t status = (exp);						\
+      if (status < B_OK)						      \
+	return status;							\
+    }									 \
+  while (0)
+
+#define RETURN_VALUE_AND_SET_ERRNO_IF_FAIL(exp, val)			  \
+  do									  \
+    {									 \
+      status_t status = (exp);						\
+      if (status < B_OK)						      \
+	{								     \
+	  errno = status;						     \
+	  return (val);						       \
+	}								     \
+    }									 \
+  while (0)
+
+#define RETURN_AND_SET_ERRNO_IF_FAIL(exp)				     \
+  RETURN_VALUE_AND_SET_ERRNO_IF_FAIL (exp, -1)
+
+/* ELF definitions.  */
+
+#if B_HAIKU_32_BIT
+typedef Elf32_Sym elf_sym;
+#define ELF_ST_TYPE ELF32_ST_TYPE
+#elif B_HAIKU_64_BIT
+typedef Elf64_Sym elf_sym;
+#define ELF_ST_TYPE ELF64_ST_TYPE
+#endif
+
+/* Private structures.  */
+
+/* Derived from headers/private/system/vfs_defs.h.  */
+struct fd_info
+{
+  int number;
+  int32 open_mode;
+  dev_t device;
+  ino_t node;
+};
+
+/* Derived from headers/private/net/net_stat.h.  */
+struct net_stat
+{
+  int family;
+  int type;
+  int protocol;
+  char state[B_OS_NAME_LENGTH];
+  team_id owner;
+  struct sockaddr_storage address;
+  struct sockaddr_storage peer;
+  size_t receive_queue_size;
+  size_t send_queue_size;
+};
+
+/* Private syscalls from headers/private/system/syscalls.h.
+   Import them as weak symbols only since their names may change anytime.  */
+
+extern "C" status_t _kern_entry_ref_to_path (dev_t device, ino_t inode,
+					     const char *leaf, char *userPath,
+					     size_t pathLength)
+    __attribute__ ((weak));
+
+extern "C" status_t _kern_get_next_fd_info (team_id team, uint32 *_cookie,
+					    fd_info *info, size_t infoSize)
+    __attribute__ ((weak));
+
+extern "C" status_t _kern_get_next_socket_stat (int family, uint32 *cookie,
+						struct net_stat *stat)
+    __attribute__ ((weak));
+
+extern "C" status_t _kern_read_kernel_image_symbols (
+    image_id id, elf_sym *symbolTable, int32 *_symbolCount, char *stringTable,
+    size_t *_stringTableSize, addr_t *_imageDelta) __attribute__ ((weak));
+
+namespace haiku_nat
+{
+
+class team_debug_context;
+
+/* Expose this instead of forward declaring
+   the whole team_debug_context. */
+template <debug_nub_message message>
+[[nodiscard]]
+std::enable_if_t<std::is_same_v<haiku_nub_message_reply<message>, void>,
+		 status_t> team_send (const team_debug_context *context,
+				      haiku_nub_message_data<message> &&data);
+
+template <debug_nub_message message>
+[[nodiscard]]
+std::enable_if_t<!std::is_same_v<haiku_nub_message_reply<message>, void>,
+		 status_t> team_send (const team_debug_context *context,
+				      haiku_nub_message_data<message> &&data,
+				      haiku_nub_message_reply<message> &reply);
+
+/* Utility function, defined below.  */
+static void convert_image_info (const ::image_info &haiku_info,
+				image_info &info);
+
+class thread_debug_context
+{
+private:
+  enum signal_status
+  {
+    /* This signal is recorded in an actual signal event
+       and will arrive to the thread when resumed.  */
+    SIGNAL_ACTUAL,
+    /* This signal is forcasted to be sent if the current event is not ignored.
+       (e.g. an exception has occurred).
+       It is not from an actual signal event.  */
+    SIGNAL_FORECASTED,
+    /* This signal (often SIGTRAP) is faked in the interface we provide to GDB.
+       It will not and should not be sent for this event.  */
+    SIGNAL_FAKED
+  };
+
+  team_debug_context *m_team = nullptr;
+  thread_id m_thread = -1;
+  bool m_stopped = false;
+  bool m_deleted = false;
+  /* Created instead of attached or otherwise existing.  */
+  bool m_created = false;
+  /* True if main executable has been unloaded.  */
+  bool m_unloaded = false;
+  /* True if we have forced this thread to stop through target_stop.  */
+  bool m_force_stopped = false;
+  /* If non-zero, the signal that Haiku would send this thread when resumed. */
+  int m_signal = 0;
+  signal_status m_signal_status;
+  struct
+  {
+    debug_cpu_state data;
+    bool valid = false;
+    bool dirty = false;
+  } m_cpu_state;
+  std::queue<std::shared_ptr<target_waitstatus> > m_events;
+
+public:
+  thread_debug_context () = default;
+  thread_debug_context (const thread_debug_context &other) = delete;
+  thread_debug_context (thread_debug_context &&other)
+      : m_team (other.m_team), m_thread (other.m_thread),
+	m_stopped (other.m_stopped), m_deleted (other.m_deleted),
+	m_created (other.m_created), m_unloaded (other.m_unloaded),
+	m_force_stopped (other.m_force_stopped), m_signal (other.m_signal),
+	m_signal_status (other.m_signal_status),
+	m_cpu_state (other.m_cpu_state), m_events (std::move (other.m_events))
+  {
+    other.m_team = nullptr;
+    other.m_thread = -1;
+    other.m_stopped = false;
+    other.m_deleted = false;
+    other.m_created = false;
+    other.m_unloaded = false;
+    other.m_force_stopped = false;
+    other.m_signal = 0;
+    other.m_cpu_state.valid = false;
+  }
+
+  [[nodiscard]]
+  status_t
+  initialize (thread_id thread, team_debug_context *team, bool created)
+  {
+    if (m_thread >= 0)
+      return B_NOT_ALLOWED;
+    m_thread = thread;
+    m_team = team;
+    m_created = created;
+    return B_OK;
+  }
+
+  bool
+  has_events () const
+  {
+    return m_thread >= 0 && !m_events.empty ();
+  }
+  bool
+  can_resume () const
+  {
+    return m_stopped && (has_events () || !m_deleted);
+  }
+  bool
+  is_stopped () const
+  {
+    return m_stopped;
+  }
+  bool
+  is_deleted () const
+  {
+    return m_deleted;
+  }
+
+  thread_id
+  thread () const
+  {
+    return m_thread;
+  }
+
+  [[nodiscard]]
+  status_t
+  enqueue (debug_debugger_message message,
+	   const debug_debugger_message_data &data,
+	   const std::function<
+	       status_t (const std::shared_ptr<target_waitstatus> &)> callback)
+  {
+    if (m_thread < 0)
+      return B_NOT_INITIALIZED;
+
+    std::shared_ptr<target_waitstatus> gdbstatus;
+
+    const auto make = [&] () -> target_waitstatus & {
+      gdbstatus = std::make_shared<target_waitstatus> ();
+      return *gdbstatus;
+    };
+
+    const auto add = [&] () {
+      RETURN_IF_FAIL (callback (gdbstatus));
+      m_events.emplace (std::move (gdbstatus));
+      return B_OK;
+    };
+
+    const auto store_cpu = [&] (const debug_cpu_state &state) {
+      m_cpu_state.data = state;
+      m_cpu_state.valid = true;
+      m_cpu_state.dirty = false;
+    };
+
+    /* For all known Haiku debugger events,
+       the related thread should be stopped. */
+    m_stopped = true;
+
+    /* Default signal status.  */
+    m_signal = SIGTRAP;
+    m_signal_status = SIGNAL_FAKED;
+
+    switch (message)
+      {
+      case B_DEBUGGER_MESSAGE_THREAD_DEBUGGED:
+	haiku_nat_debug_printf ("THREAD_DEBUGGED: team=%i, thread=%i",
+				data.origin.team, data.origin.thread);
+
+	if (m_created)
+	  {
+	    make ().set_thread_created ();
+	    RETURN_IF_FAIL (add ());
+
+	    m_created = false;
+	  }
+	else if (m_force_stopped)
+	  {
+	    /* A thread that has been requested to stop by GDB with
+		     target_stop, and it stopped cleanly, so report as SIG0. */
+	    make ().set_stopped (GDB_SIGNAL_0);
+	    RETURN_IF_FAIL (add ());
+
+	    m_force_stopped = false;
+	  }
+	else
+	  {
+	    make ().set_stopped (GDB_SIGNAL_TRAP);
+	    RETURN_IF_FAIL (add ());
+	  }
+	break;
+      case B_DEBUGGER_MESSAGE_DEBUGGER_CALL:
+	haiku_nat_debug_printf (
+	    "DEBUGGER_CALL: team=%i, thread=%i, message=%p", data.origin.team,
+	    data.origin.thread, data.debugger_call.message);
+	make ().set_stopped (GDB_SIGNAL_TRAP);
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_BREAKPOINT_HIT:
+	haiku_nat_debug_printf ("BREAKPOINT_HIT: team=%i, thread=%i",
+				data.origin.team, data.origin.thread);
+
+	store_cpu (data.breakpoint_hit.cpu_state);
+
+	make ().set_stopped (GDB_SIGNAL_TRAP);
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_WATCHPOINT_HIT:
+	haiku_nat_debug_printf ("WATCHPOINT_HIT: team=%i, thread=%i",
+				data.origin.team, data.origin.thread);
+
+	store_cpu (data.watchpoint_hit.cpu_state);
+
+	make ().set_stopped (GDB_SIGNAL_TRAP);
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_SINGLE_STEP:
+	haiku_nat_debug_printf ("SINGLE_STEP: team=%i, thread=%i",
+				data.origin.team, data.origin.thread);
+
+	store_cpu (data.single_step.cpu_state);
+
+	make ().set_stopped (GDB_SIGNAL_TRAP);
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_PRE_SYSCALL:
+	haiku_nat_debug_printf ("PRE_SYSCALL: team=%i, thread=%i, syscall=%i",
+				data.origin.team, data.origin.thread,
+				data.pre_syscall.syscall);
+	make ().set_syscall_entry (data.pre_syscall.syscall);
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_POST_SYSCALL:
+	haiku_nat_debug_printf ("POST_SYSCALL: team=%i, thread=%i, syscall=%i",
+				data.origin.team, data.origin.thread,
+				data.post_syscall.syscall);
+	make ().set_syscall_return (data.post_syscall.syscall);
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_SIGNAL_RECEIVED:
+	haiku_nat_debug_printf (
+	    "SIGNAL_RECEIVED: team=%i, thread=%i, signal=%i, deadly=%i",
+	    data.origin.team, data.origin.thread, data.signal_received.signal,
+	    data.signal_received.deadly);
+
+	m_signal = data.signal_received.signal;
+	m_signal_status = SIGNAL_ACTUAL;
+
+	/* Do NOT set the signalled event here, even when the signal is marked
+	   "deadly" by Haiku. GDB may still interrupt these signals and do
+	   something else, keeping the inferior alive. This is how debugger
+	   pause and interrupt operations work.  */
+	make ().set_stopped (
+	    gdb_signal_from_host (data.signal_received.signal));
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_EXCEPTION_OCCURRED:
+	haiku_nat_debug_printf (
+	    "EXCEPTION_OCCURRED: team=%i, thread=%i, exception=%i, signal=%i",
+	    data.origin.team, data.origin.thread,
+	    (int)data.exception_occurred.exception,
+	    data.exception_occurred.signal);
+
+	m_signal = data.exception_occurred.signal;
+	m_signal_status = SIGNAL_FORECASTED;
+
+	make ().set_stopped (
+	    gdb_signal_from_host (data.exception_occurred.signal));
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_TEAM_CREATED:
+	haiku_nat_debug_printf (
+	    "TEAM_CREATED: team=%i, thread=%i, new_team=%i", data.origin.team,
+	    data.origin.thread, data.team_created.new_team);
+
+	make ().set_forked (ptid_t (data.team_created.new_team));
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_TEAM_DELETED:
+	haiku_nat_debug_printf ("TEAM_DELETED: team=%i, status=%i",
+				data.origin.team, data.team_deleted.status);
+
+	/* Thread should also be gone with the team.  */
+	m_deleted = true;
+
+	if (data.team_deleted.signal >= 0)
+	  make ().set_signalled (
+	      gdb_signal_from_host (data.team_deleted.signal));
+	else
+	  make ().set_exited (data.team_deleted.status);
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_TEAM_EXEC:
+	haiku_nat_debug_printf (
+	    "TEAM_EXEC: team=%i, thread=%i, image_event=%i", data.origin.team,
+	    data.origin.thread, data.team_exec.image_event);
+
+	/* This event does not give us the full path of the executable,
+	   which the corresponding GDB event requires.
+
+	   Furthermore, after this event, the new process does not take
+	   control yet. We would need to wait for runtime_loader to
+	   complete its rituals and finally fire up a IMAGE_CREATED
+	   event for the main app executable.  */
+	m_unloaded = true;
+
+	make ().set_spurious ();
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_THREAD_CREATED:
+	haiku_nat_debug_printf (
+	    "THREAD_CREATED: team=%i, thread=%i, new_thread=%i",
+	    data.origin.team, data.origin.thread,
+	    data.thread_created.new_thread);
+
+	/* Ignore this event. GDB expects THREAD_CREATED to be owned by
+	   the new thread, not the old one. We report THREAD_CREATED on the
+	   first event owned by the new thread, which is THREAD_DEBUGGED.  */
+
+	make ().set_spurious ();
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_THREAD_DELETED:
+	haiku_nat_debug_printf (
+	    "THREAD_DELETED: team=%i, thread=%i, status=%i", data.origin.team,
+	    data.origin.thread, data.thread_deleted.status);
+
+	/* There might still be events for this thread, but we can no longer
+	   resume or otherwise communicate with the thread.  */
+	m_deleted = true;
+
+	make ().set_thread_exited (data.thread_deleted.status);
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_IMAGE_CREATED:
+	haiku_nat_debug_printf (
+	    "IMAGE_CREATED: team=%i, thread=%i, image_event=%i, name=%s",
+	    data.origin.team, data.origin.thread,
+	    data.image_created.image_event, data.image_created.info.name);
+
+	if (m_unloaded)
+	  {
+	    /* The app is fully loaded. Emit an EXECD event.  */
+	    if (data.image_created.info.type == B_APP_IMAGE)
+	      {
+		m_unloaded = false;
+
+		make ().set_execd (
+		    make_unique_xstrdup (data.image_created.info.name));
+		RETURN_IF_FAIL (add ());
+
+		/* Cause GDB to refresh its library list.  */
+		make ().set_loaded ();
+		RETURN_IF_FAIL (add ());
+	      }
+	    else
+	      {
+		/* Continue ignoring until we have our executable.  */
+		make ().set_spurious ();
+		RETURN_IF_FAIL (add ());
+	      }
+	  }
+	else
+	  {
+	    make ().set_loaded ();
+	    RETURN_IF_FAIL (add ());
+	  }
+
+	break;
+      case B_DEBUGGER_MESSAGE_IMAGE_DELETED:
+	haiku_nat_debug_printf (
+	    "IMAGE_DELETED: team=%i, thread=%i, image_event=%i, name=%s",
+	    data.origin.team, data.origin.thread,
+	    data.image_deleted.image_event, data.image_deleted.info.name);
+
+	if (m_unloaded)
+	  {
+	    make ().set_spurious ();
+	    RETURN_IF_FAIL (add ());
+	  }
+	else
+	  {
+	    /* Send TARGET_WAITKIND_LOADED here as well, as it causes the
+	       shared libraries list to be updated.  */
+	    make ().set_loaded ();
+	    RETURN_IF_FAIL (add ());
+	  }
+	break;
+      case B_DEBUGGER_MESSAGE_PROFILER_UPDATE:
+	haiku_nat_debug_printf ("PROFILER_UPDATE: team=%i, thread=%i",
+				data.origin.team, data.origin.thread);
+
+	/* How did we even get here?  */
+	make ().set_spurious ();
+	RETURN_IF_FAIL (add ());
+	break;
+      case B_DEBUGGER_MESSAGE_HANDED_OVER:
+	haiku_nat_debug_printf (
+	    "HANDED_OVER: team=%i, thread=%i, causing_thread=%i",
+	    data.origin.team, data.origin.thread,
+	    data.handed_over.causing_thread);
+
+	/* How did we even get here?  */
+	make ().set_spurious ();
+	RETURN_IF_FAIL (add ());
+	break;
+      default:
+	haiku_nat_debug_printf ("Unimplemented debugger message code: %i",
+				message);
+
+	make ().set_spurious ();
+	RETURN_IF_FAIL (add ());
+	break;
+      }
+
+    return B_OK;
+  }
+
+  [[nodiscard]]
+  status_t
+  dequeue (target_waitstatus *ourstatus)
+  {
+    if (m_thread < 0)
+      return B_NOT_INITIALIZED;
+    if (m_events.empty ())
+      return B_BUSY;
+
+    /* Not a real dequeue request, just peeking.  */
+    if (ourstatus == nullptr)
+      return B_OK;
+
+    *ourstatus = std::move (*m_events.front ());
+    m_events.pop ();
+    return B_OK;
+  }
+
+  [[nodiscard]]
+  status_t
+  resume (resume_kind kind = resume_continue, int sig = 0)
+  {
+    if (m_thread < 0 || m_team == nullptr)
+      return B_NOT_INITIALIZED;
+    if (kind == resume_stop)
+      return B_BAD_VALUE;
+    if (!is_stopped ())
+      return B_BUSY;
+
+    /* Let GDB run the wait loop again.  */
+    if (has_events ())
+      return B_OK;
+
+    if (is_deleted ())
+      return B_BAD_THREAD_ID;
+
+    uint32 handle_event = B_THREAD_DEBUG_HANDLE_EVENT;
+    bool step = kind == resume_step;
+    int signal_to_send = 0;
+    int signal_to_mute = 0;
+
+    if (sig != 0)
+      {
+	if (m_signal != 0)
+	  {
+	    if (sig == m_signal)
+	      {
+		/* Signal GDB wants is the same as what Haiku would send.  */
+		/* We do not need to do anything.  */
+
+		/*
+		TODO: Is this neccessary? Work out what waddlesplash meant.
+		if (m_signal_status == SIGNAL_FORECASTED)
+		{
+		  // the signal has not yet been sent, so we need to ignore
+		  // it only in this case, but not in the other ones
+		  signal_to_mute = m_signal;
+		}
+		*/
+	      }
+	    else
+	      {
+		/* Signal GDB wants is not the same as what Haiku intends.  */
+		/* If the signal is not faked, we need to ignore the event. */
+		if (m_signal_status != SIGNAL_FAKED)
+		  handle_event = B_THREAD_DEBUG_IGNORE_EVENT;
+
+		/* The event has already been ignored,
+		   so we don't need to mute the signal.  */
+	      }
+	  }
+      }
+    else
+      {
+	if (m_signal != 0)
+	  {
+	    /* Haiku intends to send a signal,
+	       but GDB does not want to send anything.  */
+	    /* Ignore the event if that signal is not fake.  */
+	    if (m_signal_status != SIGNAL_FAKED)
+	      handle_event = B_THREAD_DEBUG_IGNORE_EVENT;
+	  }
+	else
+	  {
+	    /* Neither Haiku nor GDB wants to send a signal.  */
+	  }
+      }
+
+    /* Flush CPU state if dirty.  */
+    if (m_cpu_state.valid && m_cpu_state.dirty)
+      {
+	RETURN_IF_FAIL (team_send<B_DEBUG_MESSAGE_SET_CPU_STATE> (
+	    m_team, { .thread = m_thread, .cpu_state = m_cpu_state.data }));
+
+	m_cpu_state.dirty = false;
+      }
+
+    /* Mute Haiku's pending signal if necessary.  */
+    if (signal_to_mute != 0)
+      {
+	RETURN_IF_FAIL (team_send<B_DEBUG_MESSAGE_SET_SIGNAL_MASKS> (
+	    m_team,
+	    { .thread = m_thread,
+	      .ignore_mask = 0,
+	      .ignore_once_mask = B_DEBUG_SIGNAL_TO_MASK (signal_to_mute),
+	      .ignore_op = B_DEBUG_SIGNAL_MASK_OR,
+	      .ignore_once_op = B_DEBUG_SIGNAL_MASK_OR }));
+      }
+
+    /* Send what GDB wants.  */
+    if (signal_to_send != 0)
+      {
+	if (send_signal (m_thread, signal_to_send) < 0)
+	  {
+	    haiku_nat_debug_printf (
+		"Failed to send signal %i to thread %i: %s", signal_to_send,
+		m_thread, strerror (errno));
+	    return errno;
+	  }
+      }
+
+    /* Actually resume the thread.  */
+    RETURN_IF_FAIL (team_send<B_DEBUG_MESSAGE_CONTINUE_THREAD> (
+	m_team, { .thread = m_thread,
+		  .handle_event = handle_event,
+		  .single_step = step }));
+
+    haiku_nat_debug_printf (
+	"Sent CONTINUE_THREAD message to thread %i (handle_event=%i, "
+	"single_step=%i)",
+	m_thread, handle_event, (int)step);
+
+    m_stopped = false;
+
+    m_cpu_state.valid = false;
+
+    return B_OK;
+  }
+
+  [[nodiscard]]
+  status_t
+  stop ()
+  {
+    if (is_stopped ())
+      return B_OK;
+
+    RETURN_AND_SET_ERRNO_IF_FAIL (debug_thread (m_thread));
+    m_force_stopped = true;
+
+    return B_OK;
+  }
+
+  [[nodiscard]]
+  status_t
+  get_cpu_state (debug_cpu_state &state, bool direct = false)
+  {
+    if (m_thread < 0)
+      return B_NOT_INITIALIZED;
+
+    if (!is_stopped ())
+      return B_BUSY;
+
+    if (!direct && m_cpu_state.valid)
+      {
+	state = m_cpu_state.data;
+	return B_OK;
+      }
+
+    if (is_deleted ())
+      return B_BAD_THREAD_ID;
+
+    debug_nub_get_cpu_state_reply reply;
+
+    RETURN_IF_FAIL (team_send<B_DEBUG_MESSAGE_GET_CPU_STATE> (
+	m_team, { .thread = m_thread }, reply));
+
+    state = m_cpu_state.data = reply.cpu_state;
+    m_cpu_state.valid = true;
+    m_cpu_state.dirty = false;
+
+    return B_OK;
+  }
+
+  [[nodiscard]]
+  status_t
+  set_cpu_state (const debug_cpu_state &state, bool direct = false)
+  {
+    if (m_thread < 0)
+      return B_NOT_INITIALIZED;
+
+    if (!is_stopped ())
+      return B_BUSY;
+
+    m_cpu_state.data = state;
+    m_cpu_state.valid = true;
+    m_cpu_state.dirty = true;
+
+    if (!direct)
+      return B_OK;
+
+    if (is_deleted ())
+      return B_BAD_THREAD_ID;
+
+    RETURN_IF_FAIL (team_send<B_DEBUG_MESSAGE_SET_CPU_STATE> (
+	m_team, { .thread = m_thread, .cpu_state = m_cpu_state.data }));
+
+    m_cpu_state.dirty = false;
+
+    return B_OK;
+  }
+};
+
+class team_debug_context
+{
+private:
+  team_id m_team = -1;
+  port_id m_nub_port = -1;
+  port_id m_debugger_port = -1;
+  port_id m_reply_port = -1;
+  int32 m_debug_flags = 0;
+  ::image_info m_app_image = { .id = -1 };
+  std::map<thread_id, thread_debug_context> m_threads;
+  std::set<thread_id> m_created_threads;
+  std::queue<std::pair<thread_id, std::weak_ptr<target_waitstatus> > >
+      m_events;
+
+  /* Cleans all invalid events at the front of the queue.
+     Returns true if there are no valid events left.  */
+  bool
+  clean_events ()
+  {
+    while (!m_events.empty () && m_events.front ().second.expired ())
+      m_events.pop ();
+    return m_events.empty ();
+  }
+
+  /* Deletes all ports.  */
+  void
+  delete_debug_ports ()
+  {
+    if (m_nub_port >= 0)
+      delete_port (m_nub_port);
+    m_nub_port = -1;
+    if (m_reply_port >= 0)
+      delete_port (m_reply_port);
+    m_reply_port = -1;
+    if (m_debugger_port >= 0)
+      delete_port (m_debugger_port);
+    m_debugger_port = -1;
+  }
+
+  [[nodiscard]]
+  status_t
+  set_debug_flags (int32 flags)
+  {
+    if (is_detached ())
+      return B_NOT_ALLOWED;
+
+    if (flags == m_debug_flags)
+      return B_OK;
+
+    RETURN_IF_FAIL (send<B_DEBUG_MESSAGE_SET_TEAM_FLAGS> ({ .flags = flags }));
+
+    m_debug_flags = flags;
+
+    return B_OK;
+  }
+
+public:
+  team_debug_context () = default;
+  team_debug_context (const team_debug_context &other) = delete;
+  team_debug_context (team_debug_context &&other)
+      : m_team (other.m_team), m_nub_port (other.m_nub_port),
+	m_debugger_port (other.m_debugger_port),
+	m_reply_port (other.m_reply_port), m_debug_flags (other.m_debug_flags),
+	m_app_image (std::move (other.m_app_image)),
+	m_threads (std::move (other.m_threads)),
+	m_created_threads (std::move (other.m_created_threads)),
+	m_events (std::move (other.m_events))
+  {
+    other.m_team = -1;
+    other.m_nub_port = -1;
+    other.m_debugger_port = -1;
+    other.m_reply_port = -1;
+    other.m_debug_flags = 0;
+    other.m_app_image.id = -1;
+  }
+
+  [[nodiscard]]
+  status_t
+  initialize (team_id team, bool load_existing)
+  {
+    if (m_team >= 0)
+      return B_NOT_ALLOWED;
+
+    /* Create the debugger port.  */
+    /* 10 is a value taken from waddlesplash's port.  */
+    m_debugger_port = create_port (10, "gdb debug");
+    if (m_debugger_port < 0)
+      {
+	haiku_nat_debug_printf ("Failed to create debugger port: %s",
+				strerror (m_debugger_port));
+	return m_debugger_port;
+      }
+
+    m_reply_port = create_port (10, "gdb debug reply");
+    if (m_reply_port < 0)
+      {
+	haiku_nat_debug_printf ("Failed to create debugger reply port: %s",
+				strerror (m_reply_port));
+	return m_reply_port;
+      }
+
+    /* Install ourselves as the team debugger.  */
+    m_nub_port = install_team_debugger (team, m_debugger_port);
+    if (m_nub_port < 0)
+      {
+	haiku_nat_debug_printf (
+	    "Failed to install ourselves as debugger for team %i: %s", team,
+	    strerror (errno));
+	return m_nub_port;
+      }
+
+    /* Set the team debug flags.  */
+    RETURN_IF_FAIL (set_debug_flags (
+	B_TEAM_DEBUG_SIGNALS |
+	/* Only set the syscall debug flags when appropriate.
+	   These events come very often and can flood the debugger
+	   with large unneccessary messages.  */
+	/* B_TEAM_DEBUG_PRE_SYSCALL | B_TEAM_DEBUG_POST_SYSCALL | */
+	B_TEAM_DEBUG_TEAM_CREATION | B_TEAM_DEBUG_THREADS | B_TEAM_DEBUG_IMAGES
+	| B_TEAM_DEBUG_STOP_NEW_THREADS));
+
+    /* We have successfully initialized, now record the team.  */
+    m_team = team;
+
+    if (load_existing)
+      {
+	/* Load existing images.  */
+	for_each_image (team, [&] (const image_info &info) {
+	  image_created (ptid_t (team, 0, team), info);
+	  return 0;
+	});
+
+	/* Debug and stop existing threads.  */
+	for_each_thread (team, [&] (const thread_info &info) {
+	  if (debug_thread (info.tid) == B_OK)
+	    {
+	      auto [thread_it, thread_is_new]
+		  = m_threads.try_emplace (info.tid);
+	      gdb_assert (thread_is_new);
+	      thread_debug_context &thread_context = thread_it->second;
+
+	      status_t status
+		  = thread_context.initialize (info.tid, this, false);
+	      gdb_assert (status == B_OK);
+	    }
+	  return 0;
+	});
+      }
+
+    haiku_nat_debug_printf (
+	"Attached team debugger: team=%i, debugger_port=%i, "
+	"reply_port=%i, nub_port=%i",
+	m_team, m_debugger_port, m_reply_port, m_nub_port);
+
+    return B_OK;
+  }
+
+  /* Checks whether this team has any events queued.  */
+  bool
+  has_events ()
+  {
+    if (m_team < 0)
+      return false;
+    return !clean_events ();
+  }
+
+  /* Checks whether this team is deleted or has been otherwise detached.  */
+  bool
+  is_detached () const
+  {
+    return (m_team >= 0) && (m_debugger_port < 0);
+  }
+
+  /* Checks whether m_app_image is valid.  */
+  bool
+  has_stored_app_image () const
+  {
+    return m_app_image.id != -1;
+  }
+
+  /* Getters.  */
+
+  team_id
+  team () const
+  {
+    return m_team;
+  }
+
+  port_id
+  debugger_port () const
+  {
+    return m_debugger_port;
+  }
+
+  const ::image_info &
+  app_image () const
+  {
+    return m_app_image;
+  }
+
+  /* Message operations.  */
+
+  template <debug_nub_message message>
+  [[nodiscard]]
+  std::enable_if_t<std::is_same_v<haiku_nub_message_reply<message>, void>,
+		   status_t>
+  send (const haiku_nub_message_data<message> &data) const
+  {
+    return haiku_send_nub_message<message> (m_nub_port, data);
+  }
+
+  template <debug_nub_message message>
+  [[nodiscard]]
+  std::enable_if_t<!std::is_same_v<haiku_nub_message_reply<message>, void>,
+		   haiku_nub_message_reply<message> >
+  send (haiku_nub_message_data<message> &&data) const
+  {
+    data.reply_port = m_reply_port;
+    return haiku_send_nub_message<message> (m_nub_port, data);
+  }
+
+  template <debug_nub_message message>
+  [[nodiscard]]
+  std::enable_if_t<!std::is_same_v<haiku_nub_message_reply<message>, void>,
+		   status_t>
+  send (haiku_nub_message_data<message> &&data,
+	haiku_nub_message_reply<message> &reply) const
+  {
+    data.reply_port = m_reply_port;
+    return haiku_send_nub_message<message> (m_nub_port, data, reply);
+  }
+
+  [[nodiscard]]
+  ssize_t
+  read (debug_debugger_message &message, debug_debugger_message_data &data,
+	bool block = true) const
+  {
+    if (m_team < 0)
+      return B_NOT_INITIALIZED;
+
+    if (is_detached ())
+      return B_NOT_ALLOWED;
+
+    ssize_t bytes_read;
+    int32 code;
+
+    do
+      {
+	bytes_read
+	    = read_port_etc (m_debugger_port, &code, &data, sizeof (data),
+			     (block ? 0 : B_RELATIVE_TIMEOUT), 0);
+      }
+    while (bytes_read == B_INTERRUPTED);
+
+    message = (debug_debugger_message)code;
+
+    return bytes_read;
+  }
+
+  bool
+  thread_alive (ptid_t ptid) const
+  {
+    if (m_team < 0)
+      return false;
+
+    if (is_detached ())
+      return false;
+
+    if (ptid.tid_p ())
+      {
+	auto it = m_threads.find (ptid.tid ());
+	if (it == m_threads.end ())
+	  return false;
+
+	return !it->second.is_deleted ();
+      }
+
+    for (const auto &[thread, thread_context] : m_threads)
+      {
+	if (!thread_context.is_deleted ())
+	  return true;
+      }
+
+    return false;
+  }
+
+  [[nodiscard]]
+  status_t
+  get_cpu_state (ptid_t ptid, debug_cpu_state &state, bool direct = false)
+  {
+    if (m_team < 0)
+      return B_NOT_INITIALIZED;
+
+    auto it = m_threads.find (ptid.tid ());
+    if (it == m_threads.end ())
+      return B_BAD_THREAD_ID;
+
+    return it->second.get_cpu_state (state, direct);
+  }
+
+  [[nodiscard]]
+  status_t
+  set_cpu_state (ptid_t ptid, const debug_cpu_state &state,
+		 bool direct = false)
+  {
+    if (m_team < 0)
+      return B_NOT_INITIALIZED;
+
+    auto it = m_threads.find (ptid.tid ());
+    if (it == m_threads.end ())
+      return B_BAD_THREAD_ID;
+
+    return it->second.set_cpu_state (state, direct);
+  }
+
+  /* Resumes each thread in the current team unless it stil has pending
+     events.  */
+  [[nodiscard]]
+  status_t
+  resume ()
+  {
+    if (m_team < 0)
+      return B_NOT_INITIALIZED;
+
+    for (auto &[thread, thread_context] : m_threads)
+      RETURN_IF_FAIL (thread_context.resume ());
+
+    return B_OK;
+  }
+
+  /* GDB interface.  */
+
+  /* Implement the resume target_ops method.  */
+  [[nodiscard]]
+  status_t
+  resume (ptid_t ptid, resume_kind kind, int sig)
+  {
+    if (m_team < 0)
+      return B_NOT_INITIALIZED;
+
+    bool catching_syscalls = is_catching_syscalls_for (ptid_t (m_team));
+
+    int resume_flags = m_debug_flags;
+    if (catching_syscalls)
+      resume_flags |= B_TEAM_DEBUG_PRE_SYSCALL | B_TEAM_DEBUG_POST_SYSCALL;
+    else
+      resume_flags &= ~(B_TEAM_DEBUG_PRE_SYSCALL | B_TEAM_DEBUG_POST_SYSCALL);
+
+    RETURN_IF_FAIL (set_debug_flags (resume_flags));
+
+    bool any_thread = !ptid.tid_p ();
+
+    if (any_thread)
+      {
+	for (auto &[thread, thread_context] : m_threads)
+	  if (thread_context.can_resume ())
+	    RETURN_IF_FAIL (thread_context.resume (kind, sig));
+      }
+    else
+      {
+	auto it = m_threads.find (ptid.tid ());
+	if (it == m_threads.end ())
+	  return B_BAD_THREAD_ID;
+	thread_debug_context &thread_context = it->second;
+	RETURN_IF_FAIL (thread_context.resume (kind, sig));
+      }
+
+    return B_OK;
+  }
+
+  /* Implement the wait target_ops method with a few differences:
+     - The requested ptid is passed by parameter and the resulting ptid is
+       passed back there on return.
+     - The function accepts a NULL ourstatus to peek and enqueue the next port
+       event without dequeuing it from the thread_debug_context.  */
+  [[nodiscard]]
+  status_t
+  wait (ptid_t &ptid, target_waitstatus *ourstatus,
+	target_wait_flags target_options)
+  {
+    if (m_team < 0)
+      return B_NOT_INITIALIZED;
+
+    const auto thread_dequeue = [&] (thread_debug_context &thread_context) {
+      thread_id thread = thread_context.thread ();
+
+      RETURN_IF_FAIL (thread_context.dequeue (ourstatus));
+      ptid = ptid_t (m_team, 0, thread);
+
+      /* In many cases, the dequeued event is at the front of the global
+	  queue.  */
+      clean_events ();
+
+      if (thread_context.is_deleted () && !thread_context.has_events ())
+	{
+	  haiku_nat_debug_printf (
+	      "removing deleted thread context: team=%i, thread=%i", m_team,
+	      thread_context.thread ());
+
+	  m_threads.erase (thread);
+	}
+
+      return B_OK;
+    };
+
+    bool any_thread = !ptid.tid_p ();
+
+    /* Try to read queued events.  */
+    if (any_thread)
+      {
+	if (!clean_events ())
+	  {
+	    auto [thread, weak_event] = std::move (m_events.front ());
+	    m_events.pop ();
+
+	    return thread_dequeue (m_threads.at (thread));
+	  }
+      }
+    else
+      {
+	thread_id thread = ptid.tid ();
+	auto it = m_threads.find (thread);
+	if (it == m_threads.end ())
+	  return B_BAD_THREAD_ID;
+	thread_debug_context &thread_context = it->second;
+	if (thread_context.has_events ())
+	  return thread_dequeue (thread_context);
+      }
+
+    debug_debugger_message message;
+    debug_debugger_message_data data;
+
+    bool block = !(target_options & TARGET_WNOHANG).raw ();
+
+    /* There are no suitable queued events, read some more.  */
+    while (true)
+      {
+	ssize_t bytes_read = read (message, data, block);
+
+	if (!block && (bytes_read == B_WOULD_BLOCK))
+	  {
+	    ptid = null_ptid;
+	    return B_OK;
+	  }
+	else if (bytes_read < B_OK)
+	  return bytes_read;
+
+	gdb_assert (data.origin.team == m_team);
+
+	haiku_nat_debug_printf ("Received debug message type %i.", message);
+
+	/* Internal bookkeeping.  */
+	switch (message)
+	  {
+	  case B_DEBUGGER_MESSAGE_TEAM_DELETED:
+	    /* Detach to prevent further read loops.  */
+	    detach (true);
+
+	    /* Set any thread value so that the event gets handled immediately
+	       by the code below.  */
+	    data.origin.thread = any_thread ? m_team : ptid.tid ();
+	    break;
+	  case B_DEBUGGER_MESSAGE_THREAD_CREATED:
+	    m_created_threads.insert (data.thread_created.new_thread);
+	    break;
+	  case B_DEBUGGER_MESSAGE_IMAGE_CREATED:
+	    {
+	      image_info info;
+	      convert_image_info (data.image_created.info, info);
+	      info.team = data.origin.team;
+
+	      image_created (ptid_t (data.origin.team, 0, data.origin.thread),
+			     info);
+
+	      if (data.image_created.info.type == B_APP_IMAGE)
+		m_app_image = data.image_created.info;
+	    }
+	    break;
+	  case B_DEBUGGER_MESSAGE_IMAGE_DELETED:
+	    {
+	      if (data.image_deleted.info.type == B_APP_IMAGE)
+		m_app_image.id = -1;
+
+	      image_info info;
+	      convert_image_info (data.image_deleted.info, info);
+	      info.team = data.origin.team;
+
+	      image_deleted (ptid_t (data.origin.team, 0, data.origin.thread),
+			     info);
+	    }
+	    break;
+	  case B_DEBUGGER_MESSAGE_TEAM_EXEC:
+	    m_app_image.id = -1;
+
+	    /* Destroy the whole existing address space.  */
+	    image_deleted (ptid_t (data.origin.team, 0, data.origin.thread),
+			   { .name = nullptr });
+	    break;
+	  case B_DEBUGGER_MESSAGE_DEBUGGER_CALL:
+	    {
+	      CORE_ADDR message_addr = (CORE_ADDR)data.debugger_call.message;
+	      std::string message_string;
+
+	      debug_nub_read_memory_reply read_memory_reply;
+	      status_t read_memory_status = B_OK;
+	      size_t chars_read = 0;
+
+	      while (true)
+		{
+		  read_memory_status = send<B_DEBUG_MESSAGE_READ_MEMORY> (
+		      { .address = (void *)message_addr,
+			B_MAX_READ_WRITE_MEMORY_SIZE },
+		      read_memory_reply);
+
+		  /* Message invalid, or nothing more to read.  */
+		  if (read_memory_reply.size == 0)
+		    break;
+
+		  chars_read = strnlen (read_memory_reply.data,
+					read_memory_reply.size);
+
+		  message_string.insert (message_string.end (),
+					 read_memory_reply.data,
+					 read_memory_reply.data + chars_read);
+
+		  /* Nothing else to read.  */
+		  if (chars_read < B_MAX_READ_WRITE_MEMORY_SIZE)
+		    break;
+		}
+
+	      if (read_memory_status < B_OK && message_string.empty ())
+		message_string
+		    = string_printf ("Thread %i called debugger(), but failed "
+				     "to get the debugger message.",
+				     (int)data.origin.thread);
+	      else
+		message_string = string_printf (
+		    "Thread %i called debugger(): %s", (int)data.origin.thread,
+		    message_string.c_str ());
+
+	      debugger_output (message_string.c_str ());
+	    }
+	    break;
+	  case B_DEBUGGER_MESSAGE_EXCEPTION_OCCURRED:
+	    {
+	      /* Best thing we can do, since Haiku does not provide any way to
+		 get the size!  */
+	      char buffer[1024];
+	      get_debug_exception_string (data.exception_occurred.exception,
+					  buffer, sizeof (buffer));
+
+	      std::string message_string
+		  = string_printf ("Thread %i caused an exception: %s",
+				   (int)data.origin.thread, buffer);
+
+	      debugger_output (message_string.c_str ());
+	    }
+	    break;
+	  case B_DEBUGGER_MESSAGE_HANDED_OVER:
+	    /* This event is sent in only two cases:
+	       - We called B_DEBUG_MESSAGE_PREPARE_HANDOVER, and another team
+	       called install_team_debugger(). This should be impossible for
+	       GDB.
+	       - We started debugging a team that has previously been attached
+	       by someone else, usually the debug server.
+
+	       Since the event is asynchronous (data.origin.thread = -1, no
+	       threads are stopped), we should silently ignore it, without
+	       even queuing a message to any thread_context.
+	       */
+	    continue;
+	  }
+
+	thread_id thread = data.origin.thread;
+	gdb_assert (thread >= 0);
+
+	auto [thread_it, thread_is_new] = m_threads.try_emplace (thread);
+	thread_debug_context &thread_context = thread_it->second;
+
+	if (thread_is_new)
+	  {
+	    RETURN_IF_FAIL (thread_context.initialize (
+		thread, this,
+		/* created = */ m_created_threads.count (thread) > 0));
+	    m_created_threads.erase (thread);
+	  }
+
+	RETURN_IF_FAIL (thread_context.enqueue (
+	    message, data,
+	    [&] (const std::shared_ptr<target_waitstatus> &event) {
+	      m_events.emplace (thread, std::weak_ptr (event));
+	      return B_OK;
+	    }));
+
+	/* At this point we have at least one event to dequeue.  */
+	if (any_thread || thread == ptid.tid ())
+	  return thread_dequeue (thread_context);
+      }
+
+    /* How did we get here?  */
+    return B_BAD_VALUE;
+  }
+
+  /* Implement the stop target_ops method.  */
+  status_t
+  stop (ptid_t ptid)
+  {
+    /* Stops specific thread if tid is non-zero.
+      Otherwise stops whole process.  */
+    bool all_threads = !ptid.tid_p ();
+
+    if (all_threads)
+      {
+	for (auto &[thread, thread_context] : m_threads)
+	  RETURN_IF_FAIL (thread_context.stop ());
+      }
+    else
+      {
+	auto it = m_threads.find (ptid.tid ());
+	if (it == m_threads.end ())
+	  return B_BAD_THREAD_ID;
+	thread_debug_context &thread_context = it->second;
+	RETURN_IF_FAIL (thread_context.stop ());
+      }
+
+    return B_OK;
+  }
+
+  /* Implement the detach target_ops method.  */
+  status_t
+  detach (bool force = false)
+  {
+    if (m_team < 0)
+      return B_NO_INIT;
+    if (is_detached ())
+      return B_BAD_VALUE;
+
+    status_t status = remove_team_debugger (m_team);
+
+    haiku_nat_debug_printf ("Removed team debugger for team %i, status=%s",
+			    m_team, strerror (status));
+
+    if (status < B_OK && !force)
+      return status;
+
+    delete_debug_ports ();
+
+    return B_OK;
+  }
+
+  /* Cleanup.  */
+  ~team_debug_context ()
+  {
+    if (m_team >= 0 && !is_detached ())
+      {
+	haiku_nat_debug_printf ("Team %i has not been detached but forgotten.",
+				m_team);
+	detach (true);
+      }
+  }
+};
+
+template <debug_nub_message message>
+[[nodiscard]]
+std::enable_if_t<std::is_same_v<haiku_nub_message_reply<message>, void>,
+		 status_t>
+team_send (const team_debug_context *context,
+	   haiku_nub_message_data<message> &&data)
+{
+  return context->send<message> (
+      std::forward<haiku_nub_message_data<message> > (data));
+}
+
+template <debug_nub_message message>
+[[nodiscard]]
+std::enable_if_t<!std::is_same_v<haiku_nub_message_reply<message>, void>,
+		 status_t>
+team_send (const team_debug_context *context,
+	   haiku_nub_message_data<message> &&data,
+	   haiku_nub_message_reply<message> &reply)
+{
+  return context->send<message> (
+      std::forward<haiku_nub_message_data<message> > (data), reply);
+}
+
+static std::map<team_id, std::shared_ptr<team_debug_context> >
+    team_debug_contexts;
+
+static std::mutex team_debug_ports_lock;
+static std::set<port_id> team_debug_ports;
+
+static event_pipe pipe_to_event_loop;
+static event_pipe pipe_to_worker;
+static thread_id async_worker_thread = -1;
+
+[[nodiscard]]
+static status_t
+get_context (team_id team, std::shared_ptr<team_debug_context> &context)
+{
+  auto it = team_debug_contexts.find (team);
+  if (it == team_debug_contexts.end ())
+    return B_BAD_TEAM_ID;
+  context = it->second;
+  return B_OK;
+}
+
+static status_t
+delete_context (const std::shared_ptr<team_debug_context> &context)
+{
+  if (team_debug_contexts.erase (context->team ()) == 0)
+    return B_BAD_TEAM_ID;
+  return B_OK;
+}
+
+/* See haiku-nat.h.  */
+
+int
+attach (pid_t pid, bool is_ours)
+{
+  haiku_nat_debug_printf ("pid=%i", pid);
+
+  std::shared_ptr<team_debug_context> context
+      = std::make_shared<team_debug_context> ();
+
+  RETURN_AND_SET_ERRNO_IF_FAIL (context->initialize (pid, !is_ours));
+
+  team_debug_ports_lock.lock ();
+  team_debug_ports.insert (context->debugger_port ());
+  team_debug_ports_lock.unlock ();
+
+  /* Record the debug entry and also release the pointer.  */
+  team_debug_contexts.try_emplace (pid, std::move (context));
+
+  if (is_async_p ())
+    pipe_to_worker.mark ();
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+void
+wait_for_debugger ()
+{
+  HAIKU_NAT_SCOPED_DEBUG_ENTER_EXIT;
+
+  ::wait_for_debugger ();
+}
+
+/* See haiku-nat.h.  */
+
+int
+get_cpu_state (ptid_t ptid, void *buffer)
+{
+  haiku_nat_debug_printf ("ptid=%s, buffer=%p", ptid.to_string ().c_str (),
+			  buffer);
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_context (ptid.pid (), context));
+
+  RETURN_AND_SET_ERRNO_IF_FAIL (
+      context->get_cpu_state (ptid, *(debug_cpu_state *)buffer));
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+set_cpu_state (ptid_t ptid, const void *buffer)
+{
+  haiku_nat_debug_printf ("ptid=%s, buffer=%p", ptid.to_string ().c_str (),
+			  buffer);
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_context (ptid.pid (), context));
+
+  RETURN_AND_SET_ERRNO_IF_FAIL (
+      context->set_cpu_state (ptid, *(const debug_cpu_state *)buffer));
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+resume (ptid_t ptid, resume_kind kind, int sig)
+{
+  haiku_nat_debug_printf ("ptid=%s, resume_kind=%i, sig=%i",
+			  ptid.to_string ().c_str (), (int)kind, sig);
+
+  if (is_async_p ())
+    pipe_to_worker.mark ();
+
+  if (ptid == minus_one_ptid)
+    {
+      for (auto &[pid, context] : team_debug_contexts)
+	{
+	  RETURN_AND_SET_ERRNO_IF_FAIL (context->resume (ptid, kind, sig));
+	}
+    }
+  else
+    {
+      std::shared_ptr<team_debug_context> context;
+      RETURN_AND_SET_ERRNO_IF_FAIL (get_context (ptid.pid (), context));
+
+      RETURN_AND_SET_ERRNO_IF_FAIL (context->resume (ptid, kind, sig));
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+ptid_t
+wait (ptid_t ptid, target_waitstatus *ourstatus,
+      target_wait_flags target_options)
+{
+  haiku_nat_debug_printf ("ptid=%s, target_options=%i",
+			  ptid.to_string ().c_str (),
+			  (int)target_options.raw ());
+
+  gdb_assert (ourstatus != nullptr);
+
+  if (is_async_p ())
+    pipe_to_event_loop.flush ();
+
+  std::shared_ptr<team_debug_context> chosen_context;
+
+  if (ptid == minus_one_ptid)
+    {
+      /* Wait for any process.  */
+      bool block = !(target_options & TARGET_WNOHANG).raw ();
+
+      std::vector<std::weak_ptr<team_debug_context> > contexts;
+      std::vector<object_wait_info> wait_infos;
+
+      while (!chosen_context)
+	{
+	  std::size_t context_count = team_debug_contexts.size ();
+
+	  contexts.clear ();
+	  contexts.reserve (context_count);
+
+	  wait_infos.clear ();
+	  wait_infos.reserve (context_count);
+
+	  for (const auto &[team, context] : team_debug_contexts)
+	    {
+	      if (context->has_events ())
+		{
+		  chosen_context = context;
+		  break;
+		}
+	      contexts.emplace_back (context);
+	      wait_infos.emplace_back (
+		  object_wait_info{ .object = context->debugger_port (),
+				    .type = B_OBJECT_TYPE_PORT,
+				    .events = B_EVENT_READ });
+	    }
+
+	  if (chosen_context)
+	    break;
+
+	  ssize_t count;
+
+	  do
+	    {
+	      count = wait_for_objects_etc (wait_infos.data (),
+					    wait_infos.size (),
+					    block ? 0 : B_RELATIVE_TIMEOUT, 0);
+	    }
+	  while (count == B_INTERRUPTED);
+
+	  if (!block && (count == B_WOULD_BLOCK || count == 0))
+	    {
+	      ourstatus->set_ignore ();
+	      return null_ptid;
+	    }
+	  else if (count < 0)
+	    {
+	      errno = count;
+	      return minus_one_ptid;
+	    }
+
+	  std::shared_ptr<team_debug_context> current_context;
+	  ptid_t wptid;
+	  status_t status;
+
+	  for (std::size_t i = 0; i < context_count; ++i)
+	    {
+	      if (contexts[i].expired ())
+		continue;
+	      if ((wait_infos[i].events & B_EVENT_READ) == 0)
+		continue;
+	      current_context = contexts[i].lock ();
+
+	      /* Peek to see if the port holds an event we actually want
+		 instead of something like HANDED_OVER.  */
+	      wptid = ptid;
+	      status = current_context->wait (wptid, nullptr, TARGET_WNOHANG);
+
+	      /* The current one cannot immediately give a valid event.  */
+	      if (status < B_OK || wptid == null_ptid)
+		continue;
+
+	      chosen_context = std::move (current_context);
+	      break;
+	    }
+	}
+
+      ptid = ptid_t (chosen_context->team ());
+
+      haiku_nat_debug_printf ("chosen ptid=%s", ptid.to_string ().c_str ());
+    }
+  else
+    {
+      /* Wait for the specified process only.  */
+      RETURN_VALUE_AND_SET_ERRNO_IF_FAIL (
+	  get_context (ptid.pid (), chosen_context), minus_one_ptid);
+    }
+
+  RETURN_VALUE_AND_SET_ERRNO_IF_FAIL (
+      chosen_context->wait (ptid, ourstatus, target_options), minus_one_ptid);
+
+  if (chosen_context->is_detached () && !chosen_context->has_events ())
+    {
+      haiku_nat_debug_printf ("removing deleted team context: team=%i",
+			      chosen_context->team ());
+      /* There's nothing left. Remove this team from our records.  */
+      delete_context (chosen_context);
+    }
+  else
+    {
+      /* There might be more events on the ports.
+
+	 A false positive is harmless as long as GDB intends to call us again.
+	 We should not mark if the team is about to exit (and therefore stopped
+	 being waited by GDB). Otherwise, nothing will flush the event loop
+	 pipe, and GDB will enter an infinite loop.
+
+	 A false negative makes GDB hang forever.  */
+      if (ptid != null_ptid && is_async_p ())
+	{
+	  /* There might be queued events in the debug context.
+	     Notify the main loop directly.  */
+	  pipe_to_event_loop.mark ();
+
+	  /* There might be unread events in the debugger ports.
+	     Notify the worker thread as well.  */
+	  pipe_to_worker.mark ();
+	}
+    }
+
+  haiku_nat_debug_printf ("ptid=%s, ourstatus=%s", ptid.to_string ().c_str (),
+			  ourstatus->to_string ().c_str ());
+
+  return ptid;
+}
+
+/* See haiku-nat.h.  */
+
+int
+kill (pid_t pid)
+{
+  haiku_nat_debug_printf ("pid=%i", pid);
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_context (pid, context));
+
+  RETURN_AND_SET_ERRNO_IF_FAIL (kill_team (pid));
+
+  /* Prevent future attempts to get events for the killed team.  */
+  delete_context (context);
+
+  ptid_t ptid;
+  target_waitstatus ourstatus;
+
+  /* Wait for the child to die.  */
+  while (!context->is_detached ())
+    {
+      ptid = ptid_t (pid);
+
+      if (!context->has_events ())
+	std::ignore = context->resume (ptid, resume_continue, 0);
+
+      gdb_assert (context->wait (ptid, &ourstatus, 0) == B_OK);
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+detach (pid_t pid)
+{
+  haiku_nat_debug_printf ("pid=%i", pid);
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_context (pid, context));
+
+  RETURN_AND_SET_ERRNO_IF_FAIL (context->detach ());
+
+  delete_context (context);
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+bool
+thread_alive (ptid_t ptid)
+{
+  haiku_nat_debug_printf ("ptid=%s", ptid.to_string ().c_str ());
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_VALUE_AND_SET_ERRNO_IF_FAIL (get_context (ptid.pid (), context),
+				      false);
+
+  return context->thread_alive (ptid);
+}
+
+/* See haiku-nat.h.  */
+
+int
+read_memory (pid_t pid, CORE_ADDR memaddr, unsigned char *myaddr,
+	     int *sizeLeft)
+{
+  haiku_nat_debug_printf ("pid=%i, memaddr=%p, myaddr=%p, size=%i", pid,
+			  (void *)memaddr, myaddr, *sizeLeft);
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_context (pid, context));
+
+  debug_nub_read_memory_reply reply;
+
+  while (*sizeLeft > 0)
+    {
+      int32 read_size
+	  = (int32)std::min (*sizeLeft, (int)B_MAX_READ_WRITE_MEMORY_SIZE);
+
+      RETURN_AND_SET_ERRNO_IF_FAIL (
+	  context->send<B_DEBUG_MESSAGE_READ_MEMORY> (
+	      { .address = (void *)memaddr, .size = read_size }, reply));
+
+      memcpy (myaddr, reply.data, reply.size);
+      memaddr += reply.size;
+      myaddr += reply.size;
+      *sizeLeft -= reply.size;
+    }
+
+  haiku_nat_debug_printf ("pid=%i, memaddr=%p success", pid, (void *)memaddr);
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+write_memory (pid_t pid, CORE_ADDR memaddr, const unsigned char *myaddr,
+	      int *sizeLeft)
+{
+  haiku_nat_debug_printf ("pid=%i, memaddr=%p, myaddr=%p, size=%i", pid,
+			  (void *)memaddr, myaddr, *sizeLeft);
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_context (pid, context));
+
+  debug_nub_write_memory data;
+  debug_nub_write_memory_reply reply;
+
+  while (*sizeLeft > 0)
+    {
+      data.address = (void *)memaddr;
+      data.size = std::min (*sizeLeft, (int)B_MAX_READ_WRITE_MEMORY_SIZE);
+      memcpy (data.data, myaddr, data.size);
+
+      /* TODO: Rollback if attempt failed?  */
+      RETURN_AND_SET_ERRNO_IF_FAIL (
+	  context->send<B_DEBUG_MESSAGE_WRITE_MEMORY> (std::move (data),
+						       reply));
+
+      memaddr += reply.size;
+      myaddr += reply.size;
+      *sizeLeft -= reply.size;
+    }
+
+  haiku_nat_debug_printf ("pid=%i, memaddr=%p success", pid, (void *)memaddr);
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+read_offsets (pid_t pid, CORE_ADDR *text, CORE_ADDR *data)
+{
+  haiku_nat_debug_printf ("pid=%i", pid);
+
+  CORE_ADDR raw_text;
+  size_t raw_text_size;
+  CORE_ADDR raw_data;
+
+  std::shared_ptr<team_debug_context> context;
+  if (get_context (pid, context) == B_OK && context->has_stored_app_image ())
+    {
+      haiku_nat_debug_printf ("reading offests from cached image");
+
+      /* Prioritize the cached image. This might be useful right after an
+	 IMAGE_CREATED event, when we have all the information we need but the
+	 same info is not yet visible to get_next_image_info.  */
+      raw_text = (CORE_ADDR)context->app_image ().text;
+      raw_text_size = context->app_image ().text_size;
+      raw_data = (CORE_ADDR)context->app_image ().data;
+    }
+  else
+    {
+      haiku_nat_debug_printf ("reading offests from system");
+
+      if (for_each_image (
+	      pid,
+	      [&] (const image_info &info) {
+		if (!info.is_main_executable)
+		  return 0;
+
+		raw_text = info.text;
+		raw_text_size = info.text_size;
+		raw_data = info.data;
+
+		return 1;
+	      },
+	      true)
+	  < 0)
+	return -1;
+    }
+
+  *text = raw_text;
+  *data = raw_data - raw_text_size;
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+bool
+thread_stopped (ptid_t ptid)
+{
+  haiku_nat_debug_printf ("ptid=%s", ptid.to_string ().c_str ());
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_VALUE_AND_SET_ERRNO_IF_FAIL (get_context (ptid.pid (), context),
+				      false);
+
+  status_t status = context
+			->send<B_DEBUG_MESSAGE_GET_CPU_STATE> (
+			    { .thread = (thread_id)ptid.tid () })
+			.error;
+
+  haiku_nat_debug_printf ("ptid=%s, status=%s", ptid.to_string ().c_str (),
+			  strerror (status));
+
+  if (status >= B_OK)
+    {
+      /* Operation only succeeds when thread is stopped.  */
+      return true;
+    }
+  else if (status == B_BAD_THREAD_STATE)
+    {
+      /* This occurs when thread is not stopped.  */
+      return false;
+    }
+  else
+    {
+      /* Some other error.  */
+      errno = status;
+      return false;
+    }
+}
+
+/* See haiku-nat.h.  */
+
+const char *
+pid_to_exec_file (pid_t pid)
+{
+  haiku_nat_debug_printf ("pid=%i", pid);
+
+  std::shared_ptr<team_debug_context> context;
+  if (get_context (pid, context) == B_OK && context->has_stored_app_image ())
+    {
+      return context->app_image ().name;
+    }
+
+  const char *result = nullptr;
+
+  for_each_image (
+      pid,
+      [&] (const image_info &info) {
+	if (!info.is_main_executable)
+	  return 0;
+
+	result = info.name;
+
+	return 1;
+      },
+      true);
+
+  return result;
+}
+
+/* See haiku-nat.h.  */
+
+const char *
+thread_name (ptid_t ptid)
+{
+  haiku_nat_debug_printf ("ptid=%s", ptid.to_string ().c_str ());
+
+  static ::thread_info info;
+  RETURN_VALUE_AND_SET_ERRNO_IF_FAIL (get_thread_info (ptid.tid (), &info),
+				      nullptr);
+
+  haiku_nat_debug_printf ("ptid=%s, name=%s", ptid.to_string ().c_str (),
+			  info.name);
+
+  return info.name;
+}
+
+/* See haiku-nat.h.  */
+
+std::string
+pid_to_str (ptid_t ptid)
+{
+  haiku_nat_debug_printf ("ptid=%s", ptid.to_string ().c_str ());
+
+  union
+  {
+    ::team_info team;
+    ::thread_info thread;
+  };
+
+  bool team_valid = get_team_info (ptid.pid (), &team) == B_OK;
+
+  std::string result
+      = team_valid ? string_printf ("team %d (%s)", ptid.pid (), team.name)
+		   : string_printf ("team %d", ptid.pid ());
+
+  if (!ptid.tid_p ())
+    return result;
+
+  bool thread_valid
+      = team_valid && (get_thread_info (ptid.tid (), &thread) == B_OK);
+
+  result += thread_valid
+		? string_printf (" thread %ld (%s)", ptid.tid (), thread.name)
+		: string_printf (" thread %ld", ptid.tid ());
+
+  return result;
+}
+
+/* See haiku-nat.h.  */
+
+int
+stop (ptid_t ptid)
+{
+  haiku_nat_debug_printf ("ptid=%s", ptid.to_string ().c_str ());
+
+  std::shared_ptr<team_debug_context> context;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_context (ptid.pid (), context));
+
+  RETURN_AND_SET_ERRNO_IF_FAIL (context->stop (ptid));
+
+  return 0;
+}
+
+#define RETURN_OR_CONTINUE(exp)					       \
+  do									  \
+    {									 \
+      switch (exp)							    \
+	{								     \
+	case -1:							      \
+	  return -1;							  \
+	case 0:							       \
+	  continue;							   \
+	case 1:							       \
+	  return 0;							   \
+	}								     \
+    }									 \
+  while (0)
+
+/* See haiku-nat.h  */
+
+bool
+is_async_p ()
+{
+  return async_worker_thread != -1;
+}
+
+/* See haiku-nat.h  */
+
+int
+async (bool enable)
+{
+  haiku_nat_debug_printf ("enable=%s", enable ? "true" : "false");
+
+  /* TODO: We might want to share some code and infrastructure here with
+     wait(). Ideally both should be backed by kernel-side event queues with
+     all the debugger ports added.
+
+     We can look at eliminating the worker thread all together in favor of
+     accessing the private _kern_event_queue_create syscall and returning that
+     FD as the async_wait_fd when Haiku finally adds support for polling event
+     queue FDs. See https://dev.haiku-os.org/ticket/18954.  */
+
+  if (enable == is_async_p ())
+    return 0;
+
+  if (!enable)
+    {
+      RETURN_AND_SET_ERRNO_IF_FAIL (
+	  send_signal (async_worker_thread, SIGKILLTHR));
+      async_worker_thread = -1;
+      pipe_to_worker.close_pipe ();
+      pipe_to_event_loop.close_pipe ();
+    }
+  else
+    {
+      constexpr auto pipe_close
+	  = [] (event_pipe *pipe) { pipe->close_pipe (); };
+
+      std::unique_ptr<event_pipe, decltype (pipe_close)>
+	  pipe_to_event_loop_closer (&pipe_to_event_loop, pipe_close);
+
+      std::unique_ptr<event_pipe, decltype (pipe_close)>
+	  pipe_to_worker_closer (&pipe_to_event_loop, pipe_close);
+
+      if (!pipe_to_event_loop.open_pipe ())
+	return -1;
+
+      if (!pipe_to_worker.open_pipe ())
+	return -1;
+
+      async_worker_thread = spawn_thread (
+	  [] (void *data) -> status_t {
+	    std::vector<object_wait_info> wait_infos;
+	    bool wait_for_ports = true;
+
+	    while (true)
+	      {
+		wait_infos.clear ();
+		wait_infos.emplace_back (
+		    object_wait_info{ .object = pipe_to_worker.event_fd (),
+				      .type = B_OBJECT_TYPE_FD,
+				      .events = B_EVENT_READ });
+
+		if (wait_for_ports)
+		  {
+		    std::lock_guard lock (team_debug_ports_lock);
+
+		    for (port_id port : team_debug_ports)
+		      {
+			wait_infos.emplace_back (
+			    object_wait_info{ .object = port,
+					      .type = B_OBJECT_TYPE_PORT,
+					      .events = B_EVENT_READ });
+		      }
+		  }
+
+		ssize_t event_count;
+
+		do
+		  {
+		    event_count = wait_for_objects (wait_infos.data (),
+						    wait_infos.size ());
+		  }
+		while (event_count == B_INTERRUPTED);
+
+		gdb_assert (event_count > 0);
+
+		if (wait_infos[0].events & B_EVENT_READ)
+		  {
+		    /* The main thread has requested us to continue.  */
+		    --event_count;
+		    pipe_to_worker.flush ();
+		    wait_for_ports = true;
+		  }
+		else
+		  {
+		    if (wait_infos[0].events != 0)
+		      {
+			/* Other events in the pipe we are uninterested in.  */
+			--event_count;
+		      }
+
+		    /* Wait for the main thread to actually read the ports
+		       before waiting again.  */
+		    wait_for_ports = false;
+		  }
+
+		/* There are ports to process in this iteration.  */
+		if (event_count > 0)
+		  {
+		    std::lock_guard lock (team_debug_ports_lock);
+
+		    for (size_t i = 1; i < wait_infos.size (); ++i)
+		      {
+			const object_wait_info &wait_info = wait_infos[i];
+			/* Remove dead ports.  */
+			if (wait_info.events & B_EVENT_INVALID)
+			  {
+			    team_debug_ports.erase (wait_info.object);
+			    --event_count;
+			  }
+		      }
+		  }
+
+		/* There are some events to read from these ports.  */
+		if (event_count > 0)
+		  pipe_to_event_loop.mark ();
+	      }
+	  },
+	  "gdb debugger port listener", B_NORMAL_PRIORITY, nullptr);
+
+      if (async_worker_thread < 0)
+	{
+	  errno = async_worker_thread;
+	  async_worker_thread = -1;
+	  return -1;
+	}
+
+      status_t status = resume_thread (async_worker_thread);
+      if (status < B_OK)
+	{
+	  send_signal (async_worker_thread, SIGKILLTHR);
+	  errno = status;
+	  async_worker_thread = -1;
+	  return -1;
+	}
+
+      /* Always trigger an initial event.  */
+      pipe_to_event_loop.mark ();
+
+      pipe_to_event_loop_closer.release ();
+      pipe_to_worker_closer.release ();
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h  */
+
+int
+async_wait_fd ()
+{
+  return pipe_to_event_loop.event_fd ();
+}
+
+static void
+convert_image_info (const ::image_info &haiku_info, image_info &info)
+{
+  info.id = haiku_info.id;
+  info.text = (CORE_ADDR)haiku_info.text;
+  info.text_size = (ULONGEST)haiku_info.text_size;
+  info.data = (CORE_ADDR)haiku_info.data;
+  info.data_size = (ULONGEST)haiku_info.data_size;
+  info.name = haiku_info.name;
+  info.sequence = haiku_info.sequence;
+  info.init_order = haiku_info.init_order;
+  info.is_main_executable = haiku_info.type == B_APP_IMAGE;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_image (pid_t pid,
+		const std::function<int (const image_info &info)> &callback,
+		bool needs_one)
+{
+  static ::image_info haiku_info;
+  static image_info info;
+
+  int32 cookie = 0;
+
+  while (get_next_image_info (pid, &cookie, &haiku_info) == B_OK)
+    {
+      convert_image_info (haiku_info, info);
+      info.team = pid;
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  if (needs_one)
+    {
+      errno = B_ENTRY_NOT_FOUND;
+      return -1;
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_area (pid_t pid,
+	       const std::function<int (const area_info &info)> &callback)
+{
+  static ::area_info haiku_info;
+  static area_info info;
+
+  ssize_t cookie = 0;
+
+  while (get_next_area_info (pid, &cookie, &haiku_info) == B_OK)
+    {
+      info.id = haiku_info.area;
+      info.name = haiku_info.name;
+      info.size = haiku_info.size;
+      info.can_read = haiku_info.protection & B_READ_AREA;
+      info.can_write = haiku_info.protection & B_WRITE_AREA;
+      info.can_exec = haiku_info.protection & B_EXECUTE_AREA;
+      info.is_stack = haiku_info.protection & B_STACK_AREA;
+      info.can_clone = haiku_info.protection & B_CLONEABLE_AREA;
+      info.team = haiku_info.team;
+      info.ram_size = haiku_info.ram_size;
+      info.copy_count = haiku_info.copy_count;
+      info.in_count = haiku_info.in_count;
+      info.out_count = haiku_info.out_count;
+      info.address = (CORE_ADDR)haiku_info.address;
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_thread (pid_t pid,
+		 const std::function<int (const thread_info &info)> &callback)
+{
+  static ::thread_info haiku_info;
+  static thread_info info;
+
+  int32 cookie = 0;
+
+  while (get_next_thread_info (pid, &cookie, &haiku_info) == B_OK)
+    {
+      info.tid = haiku_info.thread;
+      info.team = haiku_info.team;
+      info.name = haiku_info.name;
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_fd (pid_t pid,
+	     const std::function<int (const fd_info &info)> &callback)
+{
+  if (_kern_get_next_fd_info == nullptr)
+    {
+      haiku_nat_debug_printf ("Failed to issue get_next_fd_info syscall.");
+      errno = ENOSYS;
+      return -1;
+    }
+
+  static ::fd_info haiku_info;
+  static fd_info info;
+  static char path[B_PATH_NAME_LENGTH + 1];
+
+  uint32 cookie = 0;
+
+  while (
+      _kern_get_next_fd_info (pid, &cookie, &haiku_info, sizeof (haiku_info))
+      == B_OK)
+    {
+      info.number = haiku_info.number;
+      info.device = haiku_info.device;
+      info.node = haiku_info.node;
+      info.team = pid;
+
+      switch (haiku_info.open_mode & O_RWMASK)
+	{
+	case O_RDONLY:
+	  info.can_read = true;
+	  info.can_write = false;
+	  break;
+	case O_WRONLY:
+	  info.can_read = false;
+	  info.can_write = true;
+	  break;
+	case O_RDWR:
+	  info.can_read = true;
+	  info.can_write = true;
+	  break;
+	}
+
+      info.name = nullptr;
+
+      if (_kern_entry_ref_to_path != nullptr)
+	{
+	  /* This only works for directories.  */
+	  if (_kern_entry_ref_to_path (haiku_info.device, haiku_info.node,
+				       nullptr, path, B_PATH_NAME_LENGTH)
+	      >= B_OK)
+	    {
+	      path[B_PATH_NAME_LENGTH] = '\0';
+	      info.name = path;
+	    }
+	}
+      else
+	{
+	  haiku_nat_debug_printf (
+	      "Failed to issue entry_ref_to_path syscall.");
+	}
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_port (pid_t pid,
+	       const std::function<int (const port_info &info)> &callback)
+{
+  static ::port_info haiku_info;
+  static port_info info;
+
+  int32 cookie = 0;
+
+  while (get_next_port_info (pid, &cookie, &haiku_info) == B_OK)
+    {
+      info.id = haiku_info.port;
+      info.team = haiku_info.team;
+      info.name = haiku_info.name;
+      info.capacity = haiku_info.capacity;
+      info.queue_count = haiku_info.queue_count;
+      info.total_count = haiku_info.total_count;
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_sem (pid_t pid,
+	      const std::function<int (const sem_info &info)> &callback)
+{
+  static ::sem_info haiku_info;
+  static sem_info info;
+
+  int32 cookie = 0;
+
+  while (get_next_sem_info (pid, &cookie, &haiku_info) == B_OK)
+    {
+      info.id = haiku_info.sem;
+      info.team = haiku_info.team;
+      info.name = haiku_info.name;
+      info.count = haiku_info.count;
+      info.latest_holder = haiku_info.latest_holder;
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  return 0;
+}
+
+static void
+convert_team_info (const ::team_info &haiku_info, team_info &info)
+{
+  info.pid = haiku_info.team;
+  info.args = haiku_info.args;
+  info.thread_count = haiku_info.thread_count;
+  info.image_count = haiku_info.image_count;
+  info.area_count = haiku_info.area_count;
+  info.debugger_nub_thread = haiku_info.debugger_nub_thread;
+  info.debugger_nub_port = haiku_info.debugger_nub_port;
+  info.uid = haiku_info.uid;
+  info.gid = haiku_info.gid;
+  info.real_uid = haiku_info.real_uid;
+  info.real_gid = haiku_info.real_gid;
+  info.group_id = haiku_info.group_id;
+  info.session_id = haiku_info.session_id;
+  info.parent = haiku_info.parent;
+  info.name = haiku_info.name;
+  info.start_time = haiku_info.start_time;
+}
+
+/* See haiku-nat.h.  */
+
+const team_info *
+get_team (pid_t pid)
+{
+  static ::team_info haiku_info;
+  static team_info info;
+
+  RETURN_VALUE_AND_SET_ERRNO_IF_FAIL (get_team_info (pid, &haiku_info),
+				      nullptr);
+
+  convert_team_info (haiku_info, info);
+
+  return &info;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_team (const std::function<int (const team_info &info)> &callback)
+{
+  static ::team_info haiku_info;
+  static team_info info;
+
+  int32 cookie = 0;
+
+  while (get_next_team_info (&cookie, &haiku_info) == B_OK)
+    {
+      convert_team_info (haiku_info, info);
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_commpage_symbol (
+    const std::function<int (const commpage_symbol_info &info)> &callback)
+{
+  if (_kern_read_kernel_image_symbols == nullptr)
+    {
+      haiku_nat_debug_printf (
+	  "Failed to issue read_kernel_image_symbols syscall.");
+      errno = ENOSYS;
+      return -1;
+    }
+
+  image_id commpage_image = -1;
+  if (for_each_image (
+	  B_SYSTEM_TEAM,
+	  [&] (const image_info &image_info) {
+	    if (strcmp (image_info.name, "commpage") == 0)
+	      {
+		commpage_image = image_info.id;
+		return 1;
+	      }
+	    return 0;
+	  },
+	  true)
+      < 0)
+    {
+      return -1;
+    }
+
+  int32 symbol_count = 0;
+  size_t string_table_size = 0;
+  RETURN_AND_SET_ERRNO_IF_FAIL (
+      _kern_read_kernel_image_symbols (commpage_image, nullptr, &symbol_count,
+				       nullptr, &string_table_size, nullptr));
+
+  std::vector<elf_sym> symbols (symbol_count);
+  /* An additional guaranteed null terminator.  */
+  std::vector<char> string_table (string_table_size + 1);
+
+  RETURN_AND_SET_ERRNO_IF_FAIL (_kern_read_kernel_image_symbols (
+      commpage_image, symbols.data (), &symbol_count, string_table.data (),
+      &string_table_size, nullptr));
+
+  if (symbols.size () > symbol_count)
+    symbols.resize (symbol_count);
+
+  string_table.back () = '\0';
+
+  static commpage_symbol_info info;
+
+  for (const auto &sym : symbols)
+    {
+      info.name = string_table.data () + sym.st_name;
+      info.value = sym.st_value;
+      info.size = sym.st_size;
+      info.is_function = ELF_ST_TYPE (sym.st_info) == STT_FUNC;
+      info.is_object = ELF_ST_TYPE (sym.st_info) == STT_OBJECT;
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_cpu (const std::function<int (const cpu_info &info)> &callback)
+{
+  system_info sysinfo;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_system_info (&sysinfo));
+
+  size_t cpu_count = sysinfo.cpu_count;
+
+  std::vector< ::cpu_info> haiku_cpu_infos (cpu_count);
+  RETURN_AND_SET_ERRNO_IF_FAIL (
+      get_cpu_info (0, cpu_count, haiku_cpu_infos.data ()));
+
+  uint32 node_count = 0;
+  RETURN_AND_SET_ERRNO_IF_FAIL (get_cpu_topology_info (nullptr, &node_count));
+
+  std::vector< ::cpu_topology_node_info> haiku_cpu_nodes (node_count);
+  RETURN_AND_SET_ERRNO_IF_FAIL (
+      get_cpu_topology_info (haiku_cpu_nodes.data (), &node_count));
+
+  if (haiku_cpu_nodes.size () > node_count)
+    haiku_cpu_nodes.resize (node_count);
+
+  static cpu_info info;
+
+  for (const auto &node : haiku_cpu_nodes)
+    {
+      switch (node.type)
+	{
+	case B_TOPOLOGY_ROOT:
+	  switch (node.data.root.platform)
+	    {
+	    case B_CPU_x86:
+	      info.platform = "BePC";
+	      break;
+	    case B_CPU_x86_64:
+	      info.platform = "x86_64";
+	      break;
+	    case B_CPU_PPC:
+	      info.platform = "ppc";
+	      break;
+	    case B_CPU_PPC_64:
+	      info.platform = "ppc64";
+	      break;
+	    case B_CPU_M68K:
+	      info.platform = "m68k";
+	      break;
+	    case B_CPU_ARM:
+	      info.platform = "arm";
+	      break;
+	    case B_CPU_ARM_64:
+	      info.platform = "aarch64";
+	      break;
+	    case B_CPU_RISC_V:
+	      info.platform = "riscv64";
+	      break;
+	    case B_CPU_UNKNOWN:
+	    default:
+	      info.platform = "unknown";
+	      break;
+	    }
+	  break;
+	case B_TOPOLOGY_PACKAGE:
+	  switch (node.data.package.vendor)
+	    {
+	    case B_CPU_VENDOR_AMD:
+	      info.vendor = "AMD";
+	      break;
+	    case B_CPU_VENDOR_INTEL:
+	      info.vendor = "Intel";
+	      break;
+	    case B_CPU_UNKNOWN:
+	    default:
+	      info.vendor = "Unknown";
+	    }
+	  info.cache_line_size = node.data.package.cache_line_size;
+	  break;
+	case B_TOPOLOGY_CORE:
+	  info.model = node.data.core.model;
+	  info.default_frequency = node.data.core.default_frequency;
+	  break;
+	case B_TOPOLOGY_SMT:
+	  {
+	    /* The leaf node, corresponding to exactly one core.  */
+	    const ::cpu_info &haiku_info = haiku_cpu_infos.at (node.id);
+
+	    info.id = node.id;
+	    info.current_frequency = haiku_info.current_frequency;
+	    info.active_time = haiku_info.active_time;
+	    info.enabled = haiku_info.enabled;
+
+	    RETURN_OR_CONTINUE (callback (info));
+	  }
+	  break;
+	}
+    }
+
+  return 0;
+}
+
+/* See haiku-nat.h.  */
+
+int
+for_each_socket (const std::function<int (const socket_info &info)> &callback)
+{
+  if (_kern_get_next_socket_stat == nullptr)
+    {
+      haiku_nat_debug_printf ("Failed to issue get_next_socket_stat syscall.");
+      errno = ENOSYS;
+      return -1;
+    }
+
+  static net_stat haiku_info;
+  static socket_info info;
+
+  std::string family_str;
+  std::string type_str;
+  std::string address_str;
+  std::string peer_str;
+
+  uint32 cookie = 0;
+  while (_kern_get_next_socket_stat (-1, &cookie, &haiku_info) == B_OK)
+    {
+      switch (haiku_info.family)
+	{
+	case AF_UNIX:
+	  {
+	    family_str = "unix";
+
+	    const sockaddr_un *address = (sockaddr_un *)&haiku_info.address;
+	    address_str = address->sun_path;
+
+	    const sockaddr_un *peer = (sockaddr_un *)&haiku_info.peer;
+	    peer_str = peer->sun_path;
+	  }
+	  break;
+	case AF_INET:
+	  {
+	    family_str = "inet";
+
+	    static const auto format_address = [] (const sockaddr_in *addr) {
+	      std::string result;
+
+	      if (addr->sin_addr.s_addr == INADDR_ANY)
+		result = "*";
+	      else
+		result = inet_ntoa (addr->sin_addr);
+
+	      result += ":";
+
+	      if (addr->sin_port == 0)
+		result += "*";
+	      else
+		result += std::to_string (ntohs (addr->sin_port));
+
+	      return result;
+	    };
+
+	    address_str = format_address ((sockaddr_in *)&haiku_info.address);
+	    peer_str = format_address ((sockaddr_in *)&haiku_info.peer);
+	  }
+	  break;
+	case AF_INET6:
+	  family_str = "inet6";
+
+	  static const auto format_address = [] (const sockaddr_in6 *addr) {
+	    std::string result;
+
+	    static char buffer[INET6_ADDRSTRLEN];
+
+	    if (memcmp (&addr->sin6_addr, &in6addr_any, sizeof (in6_addr))
+		== 0)
+	      result = "*";
+	    else
+	      result = inet_ntop (AF_INET6, &addr->sin6_addr, buffer,
+				  sizeof (buffer));
+
+	    result = "[" + result + "]:";
+
+	    if (addr->sin6_port == 0)
+	      result += "*";
+	    else
+	      result += std::to_string (ntohs (addr->sin6_port));
+
+	    return result;
+	  };
+
+	  address_str = format_address ((sockaddr_in6 *)&haiku_info.address);
+	  peer_str = format_address ((sockaddr_in6 *)&haiku_info.peer);
+
+	  break;
+	default:
+	  family_str = std::to_string (haiku_info.family);
+	  address_str = peer_str = "-";
+	  break;
+	}
+
+      switch (haiku_info.type)
+	{
+	case SOCK_STREAM:
+	  switch (haiku_info.family)
+	    {
+	    case AF_INET:
+	    case AF_INET6:
+	      type_str = "tcp";
+	      break;
+	    default:
+	      type_str = "stream";
+	    }
+	  break;
+	case SOCK_DGRAM:
+	  switch (haiku_info.family)
+	    {
+	    case AF_INET:
+	    case AF_INET6:
+	      type_str = "udp";
+	      break;
+	    default:
+	      type_str = "dgram";
+	    }
+	  break;
+	case SOCK_RAW:
+	  type_str = "raw";
+	  break;
+	case SOCK_SEQPACKET:
+	  type_str = "seqpacket";
+	  break;
+	case SOCK_MISC:
+	  type_str = "misc";
+	  break;
+	}
+
+      info.family = family_str.c_str ();
+      info.type = type_str.c_str ();
+      info.state = haiku_info.state;
+      info.team = haiku_info.owner;
+      info.address = address_str.c_str ();
+      info.peer = peer_str.c_str ();
+      info.receive_queue_size = haiku_info.receive_queue_size;
+      info.send_queue_size = haiku_info.send_queue_size;
+
+      RETURN_OR_CONTINUE (callback (info));
+    }
+
+  return 0;
+}
+
+}
diff --git a/gdb/nat/haiku-nat.h b/gdb/nat/haiku-nat.h
new file mode 100644
index 00000000..0825be9d
--- /dev/null
+++ b/gdb/nat/haiku-nat.h
@@ -0,0 +1,429 @@
+/* Internal interfaces for the Haiku code.
+
+   Copyright (C) 2026 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/>.  */
+
+#ifndef NAT_HAIKU_NAT_H
+#define NAT_HAIKU_NAT_H
+
+#include <functional>
+
+#include <unistd.h>
+
+#include "target/resume.h"
+#include "target/wait.h"
+
+namespace haiku_nat
+{
+
+/* Attach gdb as the debugger for the process with the specified PID.
+   Returns -1 on failure and 0 on success.  */
+
+extern int attach (pid_t pid, bool is_ours);
+
+/* Halts the current process until a debugger has been attached.  */
+
+extern void wait_for_debugger ();
+
+/* Fetch registers from the inferior process.  */
+
+extern int get_cpu_state (ptid_t ptid, void *buffer);
+
+/* Store registers to the inferior process.  */
+
+extern int set_cpu_state (ptid_t ptid, const void *buffer);
+
+/* Implement the resume target_ops method.
+
+   Resume the inferior process.  */
+
+extern int resume (ptid_t ptid, resume_kind kind, int sig);
+
+/* Implement the wait target_ops method.
+
+   Wait for the inferior process or thread to change state.  Store
+   status through argument pointer STATUS.
+
+   PTID = -1 to wait for any pid to do something, PTID(pid,0,0) to
+   wait for any thread of process pid to do something.  Return ptid
+   of child, or -1 in case of error; store status through argument
+   pointer STATUS.  OPTIONS is a bit set of options defined as
+   TARGET_W* above.  If options contains TARGET_WNOHANG and there's
+   no child stop to report, return is
+   null_ptid/TARGET_WAITKIND_IGNORE.  */
+
+[[nodiscard]]
+extern ptid_t wait (ptid_t ptid, struct target_waitstatus *ourstatus,
+		    target_wait_flags target_options);
+
+/* Implement the kill target_ops method.
+
+   Kill process PROC.  Return -1 on failure, and 0 on success.  */
+
+[[nodiscard]]
+extern int kill (pid_t pid);
+
+/* Implement the detach target_ops method.
+
+   Detach from process PROC.  Return -1 on failure, and 0 on success.  */
+
+[[nodiscard]]
+extern int detach (pid_t pid);
+
+/* Implement the thread_alive target_ops method.
+
+   Return true iff the thread with process ID PID is alive.  */
+
+[[nodiscard]]
+extern bool thread_alive (ptid_t ptid);
+
+/* Implement the read_memory target_ops method.
+
+   Read LEN bytes at MEMADDR into a buffer at MYADDR.
+
+   Returns 0 on success and -1 on failure.  */
+
+[[nodiscard]]
+extern int read_memory (pid_t pid, CORE_ADDR memaddr, unsigned char *myaddr,
+			int *sizeLeft);
+
+/* Implement the write_memory target_ops method.
+
+   Write LEN bytes from the buffer at MYADDR to MEMADDR.
+
+   Returns 0 on success and -1 on failure.  */
+
+[[nodiscard]]
+extern int write_memory (pid_t pid, CORE_ADDR memaddr,
+			 const unsigned char *myaddr, int *sizeLeft);
+
+/* Implement the read_offsets target_ops method.
+
+   Reports the text, data offsets of the executable.  This is
+   needed for Haiku where the executable is relocated during load
+   time.  */
+
+[[nodiscard]]
+extern int read_offsets (pid_t pid, CORE_ADDR *text, CORE_ADDR *data);
+
+/* Implement the thread_stopped target_ops method.
+
+   Return true if THREAD is known to be stopped now.  */
+
+[[nodiscard]]
+extern bool thread_stopped (ptid_t ptid);
+
+/* Implement the pid_to_exec_file target_ops method.
+
+   Return the full absolute name of the executable file that was
+   run to create the process PID.  If the executable file cannot
+   be determined, NULL is returned.  Otherwise, a pointer to a
+   character string containing the pathname is returned.  This
+   string should be copied into a buffer by the client if the string
+   will not be immediately used, or if it must persist.   */
+
+[[nodiscard]]
+extern const char *pid_to_exec_file (pid_t pid);
+
+/* Implement the thread_name target_ops method.
+
+   Return the thread's name, or NULL if the target is unable to
+   determine it.  The returned value must not be freed by the
+   caller.  */
+
+[[nodiscard]]
+extern const char *thread_name (ptid_t ptid);
+
+/* Implement the pid_to_str target_ops method.
+
+   Converts a process id to a string.  Usually, the string just
+   contains `process xyz', but on some systems it may contain
+   `process xyz thread abc'.  */
+
+[[nodiscard]]
+extern std::string pid_to_str (ptid_t ptid);
+
+/* Implement the stop target_ops method.
+
+   Make target stop in a continuable fashion.  */
+
+[[nodiscard]]
+extern int stop (ptid_t ptid);
+
+/* Implement the is_async_p target_ops method.
+
+   Is the target in asynchronous execution mode?  */
+
+[[nodiscard]]
+extern bool is_async_p ();
+
+/* Implement the async target_ops method.
+
+   Enables/disables async target events.  */
+
+extern int async (bool enable);
+
+/* Implement the async_wait_fd target_ops method.  */
+
+[[nodiscard]]
+extern int async_wait_fd ();
+
+struct image_info
+{
+  LONGEST id;
+  CORE_ADDR text;
+  ULONGEST text_size;
+  CORE_ADDR data;
+  ULONGEST data_size;
+  const char *name;
+  LONGEST sequence;
+  LONGEST init_order;
+  pid_t team;
+  bool is_main_executable;
+};
+
+/* Calls the callback for each loaded image of the process with the specified
+   PID.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+   If needs_one is true, the function returns an error if none of the callback
+   invocations returns 1.
+
+   Returns 0 on success and -1 on failure.  */
+extern int
+for_each_image (pid_t pid,
+		const std::function<int (const image_info &info)> &callback,
+		bool needs_one = false);
+
+struct area_info
+{
+  LONGEST id;
+  const char *name;
+  ULONGEST size;
+  bool can_read : 1;
+  bool can_write : 1;
+  bool can_exec : 1;
+  bool is_stack : 1;
+  bool can_clone : 1;
+  pid_t team;
+  ULONGEST ram_size;
+  ULONGEST copy_count;
+  ULONGEST in_count;
+  ULONGEST out_count;
+  CORE_ADDR address;
+};
+
+/* Calls the callback for each mapped area of the process with the specified
+   PID.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+
+   Returns 0 on success and -1 on failure.  */
+extern int
+for_each_area (pid_t pid,
+	       const std::function<int (const area_info &info)> &callback);
+
+struct thread_info
+{
+  ptid_t::tid_type tid;
+  pid_t team;
+  const char *name;
+};
+
+/* Calls the callback for each active thread of the process with the specified
+   PID.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+
+   Returns 0 on success and -1 on failure.  */
+extern int
+for_each_thread (pid_t pid,
+		 const std::function<int (const thread_info &info)> &callback);
+
+struct fd_info
+{
+  int number;
+  bool can_read : 1;
+  bool can_write : 1;
+  LONGEST device;
+  LONGEST node;
+  pid_t team;
+  const char *name;
+};
+
+/* Calls the callback for each open file of the process with the specified PID.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+
+   Returns 0 on success and -1 on failure.  */
+extern int
+for_each_fd (pid_t pid,
+	     const std::function<int (const fd_info &info)> &callback);
+
+struct port_info
+{
+  LONGEST id;
+  pid_t team;
+  const char *name;
+  LONGEST capacity;
+  LONGEST queue_count;
+  LONGEST total_count;
+};
+
+/* Calls the callback for each open port of the process with the specified PID.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+
+   Returns 0 on success and -1 on failure.  */
+extern int
+for_each_port (pid_t pid,
+	       const std::function<int (const port_info &info)> &callback);
+
+struct sem_info
+{
+  LONGEST id;
+  pid_t team;
+  const char *name;
+  LONGEST count;
+  ptid_t::tid_type latest_holder;
+};
+
+/* Calls the callback for each semaphore of the process with the specified PID.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+
+   Returns 0 on success and -1 on failure.  */
+extern int
+for_each_sem (pid_t pid,
+	      const std::function<int (const sem_info &info)> &callback);
+
+struct team_info
+{
+  pid_t pid;
+  const char *args;
+  ULONGEST thread_count;
+  ULONGEST image_count;
+  ULONGEST area_count;
+  LONGEST debugger_nub_thread;
+  LONGEST debugger_nub_port;
+  LONGEST uid;
+  LONGEST gid;
+  LONGEST real_uid;
+  LONGEST real_gid;
+  pid_t group_id;
+  pid_t session_id;
+  pid_t parent;
+  const char *name;
+  LONGEST start_time;
+};
+
+/* Gets the team with the specified ID.
+
+   Returns a static buffer with the information on success
+   and nullptr on failure.  */
+[[nodiscard]]
+const team_info *get_team (pid_t pid);
+
+/* Calls the callback for each active team on the system.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+
+   Returns 0 on success and -1 on failure.  */
+extern int
+for_each_team (const std::function<int (const team_info &info)> &callback);
+
+struct commpage_symbol_info
+{
+  const char *name;
+  ULONGEST value;
+  ULONGEST size;
+  bool is_function : 1;
+  bool is_object : 1;
+};
+
+/* Calls the callback for each known symbol on the commpage.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+
+   Returns 0 on success and -1 on failure.  */
+extern int for_each_commpage_symbol (
+    const std::function<int (const commpage_symbol_info &info)> &callback);
+
+struct cpu_info
+{
+  LONGEST id;
+  const char *platform;
+  const char *vendor;
+  ULONGEST cache_line_size;
+  ULONGEST model;
+  ULONGEST default_frequency;
+  ULONGEST current_frequency;
+  ULONGEST active_time;
+  bool enabled;
+};
+
+/* Calls the callback for each CPU on the system.
+   The callback should return -1 on error, 0 if the loop should continue,
+   or 1 if the loop should end.
+
+   Returns 0 on success and -1 on failure.  */
+extern int
+for_each_cpu (const std::function<int (const cpu_info &info)> &callback);
+
+struct socket_info
+{
+  const char *family;
+  const char *type;
+  const char *state;
+  pid_t team;
+  const char *address;
+  const char *peer;
+  ULONGEST receive_queue_size;
+  ULONGEST send_queue_size;
+};
+
+extern int
+for_each_socket (const std::function<int (const socket_info &info)> &callback);
+
+/* Utility functions that are meant to be supplied by the embedding
+   application.  */
+
+void debugger_output (const char *message);
+
+void image_created (ptid_t ptid, const image_info &info);
+
+void image_deleted (ptid_t ptid, const image_info &info);
+
+bool is_catching_syscalls_for (ptid_t pid);
+
+}
+
+/* Tracing utility functions.  */
+
+extern bool debug_haiku_nat;
+
+/* Print a haiku-nat debug statement.  */
+
+#define haiku_nat_debug_printf(fmt, ...)				      \
+  debug_prefixed_printf_cond (debug_haiku_nat, "haiku-nat", fmt, ##__VA_ARGS__)
+
+/* Print "haiku-nat" enter/exit debug statements.  */
+
+#define HAIKU_NAT_SCOPED_DEBUG_ENTER_EXIT				     \
+  scoped_debug_enter_exit (debug_haiku_nat, "haiku-nat")
+
+#endif /* NAT_HAIKU_NAT_H */
diff --git a/gdb/nat/haiku-nub-message.c b/gdb/nat/haiku-nub-message.c
new file mode 100644
index 00000000..db72e78a
--- /dev/null
+++ b/gdb/nat/haiku-nub-message.c
@@ -0,0 +1,50 @@
+/* Haiku nub messages support.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "haiku-nub-message.h"
+
+status_t
+haiku_send_nub_message (port_id nub_port, port_id reply_port,
+			debug_nub_message message, const void *data,
+			int data_size, void *reply, int reply_size)
+{
+  /* Send message.  */
+  while (true)
+    {
+      status_t result = write_port (nub_port, message, data, data_size);
+      if (result == B_OK)
+	break;
+      if (result != B_INTERRUPTED)
+	return result;
+    }
+
+  if (!reply)
+    return B_OK;
+
+  /* Read reply.  */
+  while (true)
+    {
+      int32 code;
+      ssize_t bytesRead = read_port (reply_port, &code, reply, reply_size);
+      if (bytesRead > 0)
+	return B_OK;
+      if (bytesRead != B_INTERRUPTED)
+	return bytesRead;
+    }
+}
diff --git a/gdb/nat/haiku-nub-message.h b/gdb/nat/haiku-nub-message.h
new file mode 100644
index 00000000..5fdc409b
--- /dev/null
+++ b/gdb/nat/haiku-nub-message.h
@@ -0,0 +1,141 @@
+/* Haiku nub messages support.
+
+   Copyright (C) 2026 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/>.  */
+
+#ifndef NAT_HAIKU_NUB_MESSAGE_H
+#define NAT_HAIKU_NUB_MESSAGE_H
+
+#include "gnulib/config.h"
+
+#include <type_traits>
+
+#include <debugger.h>
+
+extern status_t haiku_send_nub_message (port_id nub_port, port_id reply_port,
+					debug_nub_message message,
+					const void *data, int data_size,
+					void *reply, int reply_size);
+
+template <debug_nub_message message> class haiku_nub_message_traits
+{
+};
+
+#define HAIKU_ASSOCIATE_MESSAGE_DATA(message, data)			   \
+  template <> class haiku_nub_message_traits<message>			 \
+  {									   \
+  public:								     \
+    typedef data data_type;						   \
+    typedef void reply_type;						  \
+  }
+
+#define HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY(message, data)		\
+  template <> class haiku_nub_message_traits<message>			 \
+  {									   \
+  public:								     \
+    typedef data data_type;						   \
+    typedef data##_reply reply_type;					  \
+  }
+
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_MESSAGE_READ_MEMORY,
+					 debug_nub_read_memory);
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_MESSAGE_WRITE_MEMORY,
+					 debug_nub_write_memory);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_MESSAGE_SET_TEAM_FLAGS,
+			      debug_nub_set_team_flags);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_MESSAGE_SET_THREAD_FLAGS,
+			      debug_nub_set_thread_flags);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_MESSAGE_CONTINUE_THREAD,
+			      debug_nub_continue_thread);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_MESSAGE_SET_CPU_STATE,
+			      debug_nub_set_cpu_state);
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_MESSAGE_GET_CPU_STATE,
+					 debug_nub_get_cpu_state);
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_MESSAGE_SET_BREAKPOINT,
+					 debug_nub_set_breakpoint);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_MESSAGE_CLEAR_BREAKPOINT,
+			      debug_nub_clear_breakpoint);
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_MESSAGE_SET_WATCHPOINT,
+					 debug_nub_set_watchpoint);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_MESSAGE_CLEAR_WATCHPOINT,
+			      debug_nub_clear_watchpoint);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_MESSAGE_SET_SIGNAL_MASKS,
+			      debug_nub_set_signal_masks);
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_MESSAGE_GET_SIGNAL_MASKS,
+					 debug_nub_get_signal_masks);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_MESSAGE_SET_SIGNAL_HANDLER,
+			      debug_nub_set_signal_handler);
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_MESSAGE_GET_SIGNAL_HANDLER,
+					 debug_nub_get_signal_handler);
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_START_PROFILER,
+					 debug_nub_start_profiler);
+HAIKU_ASSOCIATE_MESSAGE_DATA (B_DEBUG_STOP_PROFILER, debug_nub_stop_profiler);
+HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY (B_DEBUG_WRITE_CORE_FILE,
+					 debug_nub_write_core_file);
+
+#undef HAIKU_ASSOCIATE_MESSAGE_DATA
+#undef HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY
+
+template <debug_nub_message message>
+using haiku_nub_message_data =
+    typename haiku_nub_message_traits<message>::data_type;
+
+template <debug_nub_message message>
+using haiku_nub_message_reply =
+    typename haiku_nub_message_traits<message>::reply_type;
+
+template <debug_nub_message message>
+std::enable_if_t<std::is_same_v<haiku_nub_message_reply<message>, void>,
+		 status_t>
+haiku_send_nub_message (port_id nub_port,
+			const haiku_nub_message_data<message> &data)
+{
+  return haiku_send_nub_message (nub_port, -1, message, &data, sizeof (data),
+				 nullptr, 0);
+}
+
+template <debug_nub_message message>
+std::enable_if_t<!std::is_same_v<haiku_nub_message_reply<message>, void>,
+		 haiku_nub_message_reply<message> >
+haiku_send_nub_message (port_id nub_port,
+			const haiku_nub_message_data<message> &data)
+{
+  haiku_nub_message_reply<message> reply;
+  status_t result
+      = haiku_send_nub_message (nub_port, data.reply_port, message, &data,
+				sizeof (data), &reply, sizeof (reply));
+  if (result >= B_OK)
+    return reply;
+
+  reply.error = result;
+  return reply;
+}
+
+template <debug_nub_message message>
+std::enable_if_t<!std::is_same_v<haiku_nub_message_reply<message>, void>,
+		 status_t>
+haiku_send_nub_message (port_id nub_port,
+			const haiku_nub_message_data<message> &data,
+			haiku_nub_message_reply<message> &reply)
+{
+  status_t result
+      = haiku_send_nub_message (nub_port, data.reply_port, message, &data,
+				sizeof (data), &reply, sizeof (reply));
+  return (result < B_OK) ? result : reply.error;
+}
+
+#endif /* NAT_HAIKU_NUB_MESSAGE_H */
diff --git a/gdb/nat/haiku-osdata.c b/gdb/nat/haiku-osdata.c
new file mode 100644
index 00000000..177f4f3a
--- /dev/null
+++ b/gdb/nat/haiku-osdata.c
@@ -0,0 +1,445 @@
+/* Haiku-specific functions to retrieve OS data.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "gdbsupport/common-defs.h"
+#include "gdbsupport/xml-utils.h"
+
+#include "diagnostics.h"
+
+#include "nat/haiku-nat.h"
+#include "nat/haiku-osdata.h"
+
+using namespace haiku_nat;
+
+template <typename InfoT>
+int
+all_teams (int (*for_each) (pid_t, const std::function<int (const InfoT &)> &),
+	   const std::function<int (const InfoT &)> &callback)
+{
+  return for_each_team (
+      [&] (const team_info &info) { return for_each (info.pid, callback); });
+}
+
+static int
+for_each_image (pid_t pid,
+		const std::function<int (const image_info &info)> &callback)
+{
+  return for_each_image (pid, callback, false);
+}
+
+static struct osdata_type
+{
+  const char *type;
+  const char *title;
+  const char *description;
+  std::string (*take_snapshot) ();
+  std::string buffer;
+} osdata_table[] = {
+  {
+      .type = "types",
+      .title = "Types",
+      .description = "Listing of info os types you can list",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"types\">\n";
+
+	/* Start the below loop at 1, as we do not want to list
+	   ourselves.  */
+	for (int i = 1; osdata_table[i].type; ++i)
+	  string_xml_appendf (buffer,
+			      "<item>"
+			      "<column name=\"Type\">%s</column>"
+			      "<column name=\"Description\">%s</column>"
+			      "<column name=\"Title\">%s</column>"
+			      "</item>",
+			      osdata_table[i].type,
+			      osdata_table[i].description,
+			      osdata_table[i].title);
+
+	buffer += "</osdata>\n";
+
+	return buffer;
+      },
+  },
+  {
+      .type = "areas",
+      .title = "Areas",
+      .description = "Listing of all areas",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"areas\">\n";
+
+	all_teams<area_info> (for_each_area, [&] (const area_info &info) {
+	  std::string prot;
+	  if (info.can_read)
+	    prot += "r";
+	  if (info.can_write)
+	    prot += "w";
+	  if (info.can_exec)
+	    prot += "x";
+	  if (info.is_stack)
+	    prot += "s";
+	  if (info.can_clone)
+	    prot += "c";
+
+	  string_xml_appendf (
+	      buffer,
+	      "<item>"
+	      "<column name=\"team\">%s</column>"
+	      "<column name=\"id\">%s</column>"
+	      "<column name=\"name\">%s</column>"
+	      "<column name=\"address\">%s</column>"
+	      "<column name=\"size\">%s</column>"
+	      "<column name=\"prot\">%s</column>"
+	      "<column name=\"ram\">%s</column>"
+	      "<column name=\"#-cow\">%s</column>"
+	      "<column name=\"#-in\">%s</column>"
+	      "<column name=\"#-out\">%s</column>"
+	      "</item>",
+	      plongest (info.team), plongest (info.id), info.name,
+	      core_addr_to_string (info.address), pulongest (info.size),
+	      prot.c_str (), pulongest (info.ram_size),
+	      pulongest (info.copy_count), pulongest (info.in_count),
+	      pulongest (info.out_count));
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "comm",
+      .title = "Commpage symbols",
+      .description = "Listing of all symbols on the system commpage",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"comm\">\n";
+
+	for_each_commpage_symbol ([&] (const commpage_symbol_info &info) {
+	  std::string type;
+	  if (info.is_function)
+	    type += "f";
+	  if (info.is_object)
+	    type += "o";
+
+	  /* BE CAREFUL WHAT WE RETURN HERE!
+	     This operation is used mainly by haiku-tdep.c to synthesize the
+	     commpage object. Changing the format might break GDB itself. */
+	  string_xml_appendf (buffer,
+			      "<item>"
+			      "<column name=\"name\">%s</column>"
+			      "<column name=\"value\">%s</column>"
+			      "<column name=\"size\">%s</column>"
+			      "<column name=\"type\">%s</column>"
+			      "</item>",
+			      info.name, pulongest (info.value),
+			      pulongest (info.size), type.c_str ());
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "cpus",
+      .title = "CPUs",
+      .description = "Listing of all CPUs/cores on the system",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"cpus\">\n";
+
+	for_each_cpu ([&] (const cpu_info &info) {
+	  string_xml_appendf (
+	      buffer,
+	      "<item>"
+	      "<column name=\"id\">%s</column>"
+	      "<column name=\"platform\">%s</column>"
+	      "<column name=\"vendor\">%s</column>"
+	      "<column name=\"cache\">%s</column>"
+	      "<column name=\"model\">%s</column>"
+	      "<column name=\"default freq.\">%s</column>"
+	      "<column name=\"current freq.\">%s</column>"
+	      "<column name=\"active time\">%s</column>"
+	      "<column name=\"enabled\">%s</column>"
+	      "</item>",
+	      plongest (info.id), info.platform, info.vendor,
+	      pulongest (info.cache_line_size), pulongest (info.model),
+	      pulongest (info.default_frequency),
+	      pulongest (info.current_frequency), pulongest (info.active_time),
+	      info.enabled ? "true" : "false");
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "files",
+      .title = "File descriptors",
+      .description = "Listing of all file descriptors",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"files\">\n";
+
+	all_teams<fd_info> (for_each_fd, [&] (const fd_info &info) {
+	  std::string mode;
+	  if (info.can_read)
+	    mode += "r";
+	  if (info.can_write)
+	    mode += "w";
+
+	  string_xml_appendf (
+	      buffer,
+	      "<item>"
+	      "<column name=\"team\">%s</column>"
+	      "<column name=\"file descriptor\">%s</column>"
+	      "<column name=\"mode\">%s</column>"
+	      "<column name=\"device\">%s</column>"
+	      "<column name=\"node\">%s</column>"
+	      "<column name=\"name\">%s</column>"
+	      "</item>",
+	      plongest (info.team), plongest (info.number), mode.c_str (),
+	      plongest (info.device), plongest (info.node),
+	      (info.name != nullptr) ? info.name : "(unknown)");
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "images",
+      .title = "Images",
+      .description = "Listing of all images",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"images\">\n";
+
+	all_teams<image_info> (for_each_image, [&] (const image_info &info) {
+	  string_xml_appendf (
+	      buffer,
+	      "<item>"
+	      "<column name=\"team\">%s</column>"
+	      "<column name=\"id\">%s</column>"
+	      "<column name=\"text\">%s</column>"
+	      "<column name=\"data\">%s</column>"
+	      "<column name=\"seq#\">%s</column>"
+	      "<column name=\"init#\">%s</column>"
+	      "<column name=\"name\">%s</column>"
+	      "</item>",
+	      plongest (info.team), plongest (info.id),
+	      core_addr_to_string (info.text), core_addr_to_string (info.data),
+	      plongest (info.sequence), plongest (info.init_order), info.name);
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "ports",
+      .title = "Ports",
+      .description = "Listing of all ports",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"ports\">\n";
+
+	all_teams<port_info> (for_each_port, [&] (const port_info &info) {
+	  string_xml_appendf (buffer,
+			      "<item>"
+			      "<column name=\"team\">%s</column>"
+			      "<column name=\"id\">%s</column>"
+			      "<column name=\"name\">%s</column>"
+			      "<column name=\"capacity\">%s</column>"
+			      "<column name=\"queued\">%s</column>"
+			      "<column name=\"total\">%s</column>"
+			      "</item>",
+			      plongest (info.team), plongest (info.id),
+			      info.name, plongest (info.capacity),
+			      plongest (info.queue_count),
+			      plongest (info.total_count));
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "sems",
+      .title = "Semaphores",
+      .description = "Listing of all semaphores",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"sems\">\n";
+
+	all_teams<sem_info> (for_each_sem, [&] (const sem_info &info) {
+	  string_xml_appendf (buffer,
+			      "<item>"
+			      "<column name=\"team\">%s</column>"
+			      "<column name=\"id\">%s</column>"
+			      "<column name=\"name\">%s</column>"
+			      "<column name=\"count\">%s</column>"
+			      "<column name=\"holder\">%s</column>"
+			      "</item>",
+			      plongest (info.team), plongest (info.id),
+			      info.name, plongest (info.count),
+			      plongest (info.latest_holder));
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "sockets",
+      .title = "Sockets",
+      .description = "Listing of all sockets",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"sockets\">\n";
+
+	for_each_socket ([&] (const socket_info &info) {
+	  string_xml_appendf (buffer,
+			      "<item>"
+			      "<column name=\"team\">%s</column>"
+			      "<column name=\"family\">%s</column>"
+			      "<column name=\"protocol\">%s</column>"
+			      "<column name=\"local address\">%s</column>"
+			      "<column name=\"remote address\">%s</column>"
+			      "<column name=\"state\">%s</column>"
+			      "<column name=\"recv-q\">%s</column>"
+			      "<column name=\"send-q\">%s</column>"
+			      "</item>",
+			      plongest (info.team), info.family, info.type,
+			      info.address, info.peer, info.state,
+			      pulongest (info.receive_queue_size),
+			      pulongest (info.send_queue_size));
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "teams",
+      .title = "Teams",
+      .description = "Listing of all teams",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"teams\">\n";
+
+	for_each_team ([&] (const team_info &info) {
+	  string_xml_appendf (buffer,
+			      "<item>"
+			      "<column name=\"id\">%s</column>"
+			      "<column name=\"uid\">%s</column>"
+			      "<column name=\"command\">%s</column>"
+			      "</item>",
+			      pulongest (info.pid), pulongest (info.uid),
+			      info.args);
+
+	  return 0;
+	});
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  {
+      .type = "threads",
+      .title = "Threads",
+      .description = "Listing of all threads",
+      .take_snapshot = [] () -> std::string {
+	std::string buffer = "<osdata type=\"threads\">\n";
+
+	all_teams<thread_info> (
+	    for_each_thread, [&] (const thread_info &info) {
+	      string_xml_appendf (buffer,
+				  "<item>"
+				  "<column name=\"team\">%s</column>"
+				  "<column name=\"id\">%s</column>"
+				  "<column name=\"name\">%s</column>"
+				  "</item>",
+				  plongest (info.team), plongest (info.tid),
+				  info.name);
+
+	      return 0;
+	    });
+
+	buffer += "</osdata>\n";
+	return buffer;
+      },
+  },
+  /* TODO: There are also private syscalls for disk info,
+     but let's just ignore them for now.  */
+  { NULL }
+};
+
+/*  Copies up to LEN bytes in READBUF from offset OFFSET in OSD->BUFFER.
+    If OFFSET is zero, first calls OSD->TAKE_SNAPSHOT.  */
+
+static LONGEST
+common_getter (osdata_type *osd, gdb_byte *readbuf, ULONGEST offset,
+	       ULONGEST len)
+{
+  gdb_assert (readbuf);
+
+  if (offset == 0)
+    osd->buffer = osd->take_snapshot ();
+
+  if (offset >= osd->buffer.size ())
+    {
+      /* Done.  Get rid of the buffer.  */
+      osd->buffer.clear ();
+      return 0;
+    }
+
+  len = std::min (len, osd->buffer.size () - offset);
+  memcpy (readbuf, &osd->buffer[offset], len);
+
+  return len;
+}
+
+LONGEST
+haiku_common_xfer_osdata (const char *annex, gdb_byte *readbuf,
+			  ULONGEST offset, ULONGEST len)
+{
+  if (!annex || *annex == '\0')
+    {
+      return common_getter (&osdata_table[0], readbuf, offset, len);
+    }
+  else
+    {
+      int i;
+
+      for (i = 0; osdata_table[i].type; ++i)
+	{
+	  if (strcmp (annex, osdata_table[i].type) == 0)
+	    return common_getter (&osdata_table[i], readbuf, offset, len);
+	}
+
+      return 0;
+    }
+}
diff --git a/gdb/nat/haiku-osdata.h b/gdb/nat/haiku-osdata.h
new file mode 100644
index 00000000..176814e7
--- /dev/null
+++ b/gdb/nat/haiku-osdata.h
@@ -0,0 +1,26 @@
+/* Haiku-specific functions to retrieve OS data.
+
+   Copyright (C) 2026 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/>.  */
+
+#ifndef NAT_HAIKU_OSDATA_H
+#define NAT_HAIKU_OSDATA_H
+
+extern LONGEST haiku_common_xfer_osdata (const char *annex, gdb_byte *readbuf,
+					 ULONGEST offset, ULONGEST len);
+
+#endif /* NAT_HAIKU_OSDATA_H */
diff --git a/gdbserver/Makefile.in b/gdbserver/Makefile.in
index e45c89dc..890f05cb 100644
--- a/gdbserver/Makefile.in
+++ b/gdbserver/Makefile.in
@@ -592,6 +592,12 @@ gdbreplay.o: gdbreplay.cc
 	$(ECHO_CXX) $(COMPILE.pre) $(INTERNAL_CFLAGS) $(CXXFLAGS) \
 		-include gdbsupport/common-defs.h $(COMPILE.post) $<
 
+# Rule for Haiku files.  This is the same as COMPILE, but does not include
+# server.h to avoid name clashes with Haiku system structs.
+nat/haiku-%.o: ../gdb/nat/haiku-%.c
+	$(ECHO_CXX) $(COMPILE.pre) $(INTERNAL_CFLAGS) $(CXXFLAGS) \
+		$(COMPILE.post) -x c++ $<
+
 #
 # Dependency tracking.
 #
diff --git a/gdbserver/configure b/gdbserver/configure
index 0159da6b..4c008ced 100755
--- a/gdbserver/configure
+++ b/gdbserver/configure
@@ -8487,7 +8487,7 @@ return socketpair ();
   return 0;
 }
 _ACEOF
-for ac_lib in '' socket; do
+for ac_lib in '' socket network; do
   if test -z "$ac_lib"; then
     ac_res="none required"
   else
diff --git a/gdbserver/configure.srv b/gdbserver/configure.srv
index 7bdd92e3..85435c57 100644
--- a/gdbserver/configure.srv
+++ b/gdbserver/configure.srv
@@ -414,6 +414,15 @@ case "${gdbserver_host}" in
 			srv_tgtobj="${srv_tgtobj} nat/netbsd-nat.o"
 			srv_tgtobj="${srv_tgtobj} arch/amd64.o"
 			;;
+  x86_64-*-haiku*)	srv_regobj=""
+			srv_tgtobj="haiku-low.o haiku-amd64-low.o fork-child.o"
+			srv_tgtobj="${srv_tgtobj} nat/fork-inferior.o"
+			srv_tgtobj="${srv_tgtobj} nat/haiku-debug.o"
+			srv_tgtobj="${srv_tgtobj} nat/haiku-nat.o"
+			srv_tgtobj="${srv_tgtobj} nat/haiku-nub-message.o"
+			srv_tgtobj="${srv_tgtobj} nat/haiku-osdata.o"
+			srv_tgtobj="${srv_tgtobj} arch/amd64.o"
+			;;
 
   xtensa*-*-linux*)	srv_regobj=reg-xtensa.o
 			srv_tgtobj="$srv_linux_obj linux-xtensa-low.o"
diff --git a/gdbserver/haiku-amd64-low.cc b/gdbserver/haiku-amd64-low.cc
new file mode 100644
index 00000000..62480b71
--- /dev/null
+++ b/gdbserver/haiku-amd64-low.cc
@@ -0,0 +1,262 @@
+/* Copyright (C) 2026 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/>.  */
+
+#include "server.h"
+#include "target.h"
+
+#include "haiku-low.h"
+
+#include "arch/amd64.h"
+#include "gdbsupport/x86-xstate.h"
+#include "tdesc.h"
+#include "x86-tdesc.h"
+
+#include "nat/haiku-nat.h"
+
+#include <array>
+
+/* Very conservative inclusion of Haiku headers to prevent name clashes.  */
+#include <SupportDefs.h>
+#include <arch/x86_64/arch_debugger.h>
+
+/* Register numbers of various important registers.  */
+
+enum amd64_regnum
+{
+  AMD64_RAX_REGNUM,      /* %rax */
+  AMD64_RBX_REGNUM,      /* %rbx */
+  AMD64_RCX_REGNUM,      /* %rcx */
+  AMD64_RDX_REGNUM,      /* %rdx */
+  AMD64_RSI_REGNUM,      /* %rsi */
+  AMD64_RDI_REGNUM,      /* %rdi */
+  AMD64_RBP_REGNUM,      /* %rbp */
+  AMD64_RSP_REGNUM,      /* %rsp */
+  AMD64_R8_REGNUM,       /* %r8 */
+  AMD64_R9_REGNUM,       /* %r9 */
+  AMD64_R10_REGNUM,      /* %r10 */
+  AMD64_R11_REGNUM,      /* %r11 */
+  AMD64_R12_REGNUM,      /* %r12 */
+  AMD64_R13_REGNUM,      /* %r13 */
+  AMD64_R14_REGNUM,      /* %r14 */
+  AMD64_R15_REGNUM,      /* %r15 */
+  AMD64_RIP_REGNUM,      /* %rip */
+  AMD64_EFLAGS_REGNUM,   /* %eflags */
+  AMD64_CS_REGNUM,       /* %cs */
+  AMD64_SS_REGNUM,       /* %ss */
+  AMD64_DS_REGNUM,       /* %ds */
+  AMD64_ES_REGNUM,       /* %es */
+  AMD64_FS_REGNUM,       /* %fs */
+  AMD64_GS_REGNUM,       /* %gs */
+  AMD64_ST0_REGNUM = 24, /* %st0 */
+  AMD64_ST1_REGNUM,      /* %st1 */
+  AMD64_FCTRL_REGNUM = AMD64_ST0_REGNUM + 8,
+  AMD64_FSTAT_REGNUM = AMD64_ST0_REGNUM + 9,
+  AMD64_FTAG_REGNUM = AMD64_ST0_REGNUM + 10,
+  AMD64_XMM0_REGNUM = 40, /* %xmm0 */
+  AMD64_XMM1_REGNUM,      /* %xmm1 */
+  AMD64_MXCSR_REGNUM = AMD64_XMM0_REGNUM + 16,
+  AMD64_YMM0H_REGNUM, /* %ymm0h */
+  AMD64_YMM15H_REGNUM = AMD64_YMM0H_REGNUM + 15,
+  AMD64_BND0R_REGNUM = AMD64_YMM15H_REGNUM + 1,
+  AMD64_BND3R_REGNUM = AMD64_BND0R_REGNUM + 3,
+  AMD64_BNDCFGU_REGNUM,
+  AMD64_BNDSTATUS_REGNUM,
+  AMD64_XMM16_REGNUM,
+  AMD64_XMM31_REGNUM = AMD64_XMM16_REGNUM + 15,
+  AMD64_YMM16H_REGNUM,
+  AMD64_YMM31H_REGNUM = AMD64_YMM16H_REGNUM + 15,
+  AMD64_K0_REGNUM,
+  AMD64_K7_REGNUM = AMD64_K0_REGNUM + 7,
+  AMD64_ZMM0H_REGNUM,
+  AMD64_ZMM31H_REGNUM = AMD64_ZMM0H_REGNUM + 31,
+  AMD64_PKRU_REGNUM,
+  AMD64_FSBASE_REGNUM,
+  AMD64_GSBASE_REGNUM
+};
+
+/* Number of general purpose registers.  */
+#define AMD64_NUM_GREGS 24
+
+#define AMD64_NUM_REGS (AMD64_GSBASE_REGNUM + 1)
+
+/* At haiku_amd64_reg_offsets[REGNUM] you'll find the offset in `struct
+   debug_cpu_state' where the GDB register REGNUM is stored. */
+static constexpr auto haiku_amd64_reg_offsets = [] () constexpr {
+  std::array<int, AMD64_NUM_GREGS> result = {};
+
+  /* Set up the register offset table.  */
+#define HAIKU_DECLARE_REG_OFFSET(gdbreg, haikureg)			    \
+  result[AMD64_##gdbreg##_REGNUM]					     \
+      = offsetof (struct x86_64_debug_cpu_state, haikureg)
+
+  HAIKU_DECLARE_REG_OFFSET (RAX, rax);
+  HAIKU_DECLARE_REG_OFFSET (RBX, rbx);
+  HAIKU_DECLARE_REG_OFFSET (RCX, rcx);
+  HAIKU_DECLARE_REG_OFFSET (RDX, rdx);
+  HAIKU_DECLARE_REG_OFFSET (RSI, rsi);
+  HAIKU_DECLARE_REG_OFFSET (RDI, rdi);
+  HAIKU_DECLARE_REG_OFFSET (RBP, rbp);
+  HAIKU_DECLARE_REG_OFFSET (RSP, rsp);
+  HAIKU_DECLARE_REG_OFFSET (R8, r8);
+  HAIKU_DECLARE_REG_OFFSET (R9, r9);
+  HAIKU_DECLARE_REG_OFFSET (R10, r10);
+  HAIKU_DECLARE_REG_OFFSET (R11, r11);
+  HAIKU_DECLARE_REG_OFFSET (R12, r12);
+  HAIKU_DECLARE_REG_OFFSET (R13, r13);
+  HAIKU_DECLARE_REG_OFFSET (R14, r14);
+  HAIKU_DECLARE_REG_OFFSET (R15, r15);
+  HAIKU_DECLARE_REG_OFFSET (RIP, rip);
+  HAIKU_DECLARE_REG_OFFSET (EFLAGS, rflags);
+  HAIKU_DECLARE_REG_OFFSET (CS, cs);
+  HAIKU_DECLARE_REG_OFFSET (SS, ss);
+  HAIKU_DECLARE_REG_OFFSET (DS, ds);
+  HAIKU_DECLARE_REG_OFFSET (ES, es);
+  HAIKU_DECLARE_REG_OFFSET (FS, fs);
+  HAIKU_DECLARE_REG_OFFSET (GS, gs);
+
+#undef HAIKU_DECLARE_REG_OFFSET
+
+  return result;
+}();
+
+/* Haiku target op definitions for the amd64 architecture.  */
+
+class haiku_amd64_target : public haiku_process_target
+{
+public:
+  void fetch_registers (regcache *regcache, int regno) override;
+
+  void store_registers (regcache *regcache, int regno) override;
+
+  const gdb_byte *sw_breakpoint_from_kind (int kind, int *size) override;
+
+protected:
+  virtual void low_arch_setup (process_info *process) override;
+};
+
+/* Implement the fetch_registers target_ops method.  */
+
+void
+haiku_amd64_target::fetch_registers (regcache *regcache, int regno)
+{
+  char regs[sizeof (x86_64_debug_cpu_state)];
+
+  if (haiku_nat::get_cpu_state (current_thread->id, &regs) < 0)
+    {
+      /* This happens when the inferior is killed by another process
+	 while being stopped. The nub port has been deleted, so we cannot
+	 send the required message to get the CPU state.  */
+      haiku_nat_debug_printf ("Failed to get actual CPU state: %s",
+			      strerror (errno));
+      memset (regs, 0, sizeof (regs));
+    }
+
+  if (regno == -1)
+    {
+      for (int i = 0; i < AMD64_NUM_GREGS; ++i)
+	supply_register (regcache, i, regs + haiku_amd64_reg_offsets[i]);
+    }
+  else
+    {
+      if (regno < AMD64_NUM_GREGS)
+	supply_register (regcache, regno,
+			 regs + haiku_amd64_reg_offsets[regno]);
+      else
+	{
+	  /* For the main GDB codebase, there is a helper function,
+	     amd64_supply_fxsave that does just what we want.
+	     However, this function is not linked to gdbserver.
+
+	     We can fetch these registers by hand, but NetBSD seems fine with
+	     just the general purpose ones, so keep it stubbed for now.  */
+	  haiku_nat_debug_printf ("Trying to fetch unimplemented register #%i",
+				  regno);
+	}
+    }
+}
+
+/* Implement the store_registers target_ops method.  */
+
+void
+haiku_amd64_target::store_registers (regcache *regcache, int regno)
+{
+  char regs[sizeof (x86_64_debug_cpu_state)];
+
+  if (haiku_nat::get_cpu_state (current_thread->id, &regs) < 0)
+    {
+      haiku_nat_debug_printf ("Failed to get actual CPU state: %s",
+			      strerror (errno));
+      return;
+    }
+
+  if (regno == -1)
+    {
+      for (int i = 0; i < AMD64_NUM_GREGS; ++i)
+	collect_register (regcache, i, regs + haiku_amd64_reg_offsets[i]);
+    }
+  else
+    {
+      if (regno < AMD64_NUM_GREGS)
+	collect_register (regcache, regno,
+			  regs + haiku_amd64_reg_offsets[regno]);
+      else
+	{
+	  haiku_nat_debug_printf ("Trying to store unimplemented register #%i",
+				  regno);
+	}
+    }
+
+  if (haiku_nat::set_cpu_state (current_thread->id, &regs) < 0)
+    perror_with_name (_("haiku_nat::set_cpu_state"));
+}
+
+const gdb_byte *
+haiku_amd64_target::sw_breakpoint_from_kind (int kind, int *size)
+{
+  /* From <private/kernel/arch/x86/arch_user_debugger.h>  */
+
+  /* DEBUG_SOFTWARE_BREAKPOINT_SIZE */
+  *size = 1;
+  /* DEBUG_SOFTWARE_BREAKPOINT */
+  static const gdb_byte x86_software_breakpoint[] = { 0xcc };
+  return x86_software_breakpoint;
+}
+
+/* Architecture-specific setup for the current process.  */
+
+void
+haiku_amd64_target::low_arch_setup (process_info *process)
+{
+  if (process == nullptr)
+    process = current_process ();
+
+  /* Set up the target description.  */
+  target_desc_up tdesc = amd64_create_target_description (X86_XSTATE_AVX_MASK,
+							false, false, false);
+
+  init_target_desc (tdesc.get (), amd64_expedite_regs, GDB_OSABI_HAIKU);
+
+  process->tdesc = tdesc.release ();
+}
+
+/* The singleton target ops object.  */
+
+static haiku_amd64_target the_haiku_amd64_target;
+
+/* The Haiku target ops object.  */
+
+haiku_process_target *the_haiku_target = &the_haiku_amd64_target;
diff --git a/gdbserver/haiku-low.cc b/gdbserver/haiku-low.cc
new file mode 100644
index 00000000..fdd8c82d
--- /dev/null
+++ b/gdbserver/haiku-low.cc
@@ -0,0 +1,613 @@
+/* Copyright (C) 2026 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/>.  */
+
+#include "server.h"
+#include "target.h"
+
+#include "haiku-low.h"
+#include "nat/haiku-nat.h"
+#include "nat/haiku-osdata.h"
+
+#include "gdbsupport/common-debug.h"
+#include "gdbsupport/common-inferior.h"
+#include "gdbsupport/eintr.h"
+#include "nat/fork-inferior.h"
+
+int using_threads = 1;
+
+#ifdef DEVELOPMENT
+bool debug_haiku_nat = true;
+#else
+bool debug_haiku_nat = false;
+#endif
+
+/* Implement the create_inferior method of the target_ops vector.  */
+
+int
+haiku_process_target::create_inferior (const char *program,
+				       const std::string &program_args)
+{
+  haiku_nat_debug_printf ("program=%s", program);
+
+  static const auto haiku_traceme = [] () {
+    haiku_nat_debug_printf ("haiku_traceme");
+    /* This happens before the child calls exec().
+       The debugger is responsible for resuming the inferior before it
+       loads the desired target.  */
+    haiku_nat::wait_for_debugger ();
+  };
+
+  static const auto haiku_init_trace = [] (int pid) {
+    haiku_nat_debug_printf ("haiku_init_trace: pid=%i", pid);
+    if (haiku_nat::attach (pid, true) < 0)
+      trace_start_error_with_name (("haiku_nat::attach"));
+
+    /* At this stage, the child is being stopped for the first debugger event.
+       It has NOT exec'ed into the desired target yet, but is still a gdbserver
+       stuck in a wait_for_debugger() call.  */
+
+    /* Consume the initial event.  */
+    target_waitstatus ourstatus;
+    if (haiku_nat::wait (ptid_t (pid), &ourstatus, 0) == minus_one_ptid)
+      perror_with_name (_("haiku_nat::wait"));
+
+    /* Allows the child to proceed to exec.  */
+    if (haiku_nat::resume (ptid_t (pid), resume_continue, 0) < 0)
+      perror_with_name (_("haiku_nat::resume"));
+  };
+
+  client_state &cs = get_client_state ();
+  if (cs.disable_randomization)
+    get_environ ()->set ("DISABLE_ASLR", "1");
+  else
+    get_environ ()->unset ("DISABLE_ASLR");
+
+  pid_t pid = fork_inferior (program, program_args.c_str (),
+			     get_environ ()->envp (), haiku_traceme,
+			     haiku_init_trace, nullptr, nullptr, nullptr);
+
+  add_process (pid, 0);
+
+  post_fork_inferior (pid, program);
+
+  return pid;
+}
+
+/* Implement the post_create_inferior target_ops method.  */
+
+void
+haiku_process_target::post_create_inferior ()
+{
+  low_arch_setup ();
+}
+
+/* Implement the attach target_ops method.  */
+
+int
+haiku_process_target::attach (unsigned long pid)
+{
+  /* Add the process soon since haiku_nat::attach will
+     invoke our callback to report loaded libraries.  */
+  process_info *process = add_process (pid, 1);
+
+  if (haiku_nat::attach (pid, false) < 0)
+    perror_with_name (_("haiku_nat::attach"));
+
+  low_arch_setup (process);
+
+  return 0;
+}
+
+/* Implement the resume target_ops method.  */
+
+void
+haiku_process_target::resume (thread_resume *resume_info, size_t n)
+{
+  for (size_t i = 0; i < n; ++i)
+    {
+      if (resume_info->thread.tid_p ())
+	{
+	  thread_info *info = find_thread_ptid (ptid_t (
+	      resume_info->thread.pid (), 0, resume_info->thread.tid ()));
+	  if (info != nullptr)
+	    regcache_invalidate_thread (info);
+	}
+      else if (resume_info->thread.pid () > 0)
+	regcache_invalidate_pid (resume_info->thread.pid ());
+      else
+	regcache_invalidate ();
+
+      /* TODO: What does the step_range_[start/end] mean?  */
+      if (haiku_nat::resume (resume_info->thread, resume_info->kind,
+			     resume_info->sig)
+	  < 0)
+	{
+	  haiku_nat_debug_printf ("Failed to actually resume the thread: %s",
+				  safe_strerror (errno));
+	}
+    }
+}
+
+/* Implement the wait target_ops method.  */
+
+ptid_t
+haiku_process_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
+			    target_wait_flags target_options)
+{
+  haiku_nat_debug_printf (
+      "ptid=%s, ourstatus=%s, target_options=%i", ptid.to_string ().c_str (),
+      ourstatus->to_string ().c_str (), (int)target_options.raw ());
+
+  const auto attach_child = [&] () {
+    pid_t pid = ourstatus->child_ptid ().pid ();
+
+    process_info *process = add_process (pid, 0);
+
+    /* The new process might have other images pre-loaded.
+       Therefore, the second parameter should be false.  */
+    if (haiku_nat::attach (pid, false) < 0)
+      perror_with_name (_("haiku_nat::attach"));
+
+    low_arch_setup (process);
+
+    /* Add at least the child's main thread. Otherwise, gdbserver would
+       think we have no more inferiors attached and quit.  */
+    find_process_pid (pid)->add_thread (ptid_t (pid, 0, pid), nullptr);
+  };
+
+  client_state &cs = get_client_state ();
+
+  while (true)
+    {
+      ptid_t wptid = haiku_nat::wait (ptid, ourstatus, target_options);
+
+      if (wptid == minus_one_ptid)
+	perror_with_name (_("haiku_nat::wait"));
+
+      if (wptid == null_ptid)
+	{
+	  gdb_assert (target_options & TARGET_WNOHANG);
+	  return null_ptid;
+	}
+
+      /* Register thread in the gdbcore if a thread was not reported earlier.
+	 This is required after ::create_inferior, when the gdbcore does not
+	 know about the first internal thread.
+	 This may also happen on attach, when an event is registered on a
+	 thread that was not fully initialized during the attach stage.  */
+      if (wptid.tid () != 0 && !find_thread_ptid (wptid)
+	  && ourstatus->kind () != TARGET_WAITKIND_THREAD_EXITED)
+	find_process_pid (wptid.pid ())->add_thread (wptid, nullptr);
+
+      switch (ourstatus->kind ())
+	{
+	case TARGET_WAITKIND_EXITED:
+	case TARGET_WAITKIND_STOPPED:
+	case TARGET_WAITKIND_SIGNALLED:
+	case TARGET_WAITKIND_SYSCALL_ENTRY:
+	case TARGET_WAITKIND_SYSCALL_RETURN:
+	  /* Pass the result to the generic code.  */
+	  return wptid;
+	case TARGET_WAITKIND_LOADED:
+	  find_process_pid (wptid.pid ())->dlls_changed = true;
+
+	  /* Pass the result to the generic code.
+
+	     gdbserver core will absorb this event and convert it into a
+	     "stopped" event with GDB_SIGNAL_0.
+
+	     However, with dlls_changed set to true, when replying to the
+	     client, the message will be overwritten with a libraries changed
+	     notification, preventing GDB from actually breaking.  */
+	  return wptid;
+	case TARGET_WAITKIND_FORKED:
+	  if (cs.report_fork_events)
+	    {
+	      attach_child ();
+	      return wptid;
+	    }
+	  break;
+	case TARGET_WAITKIND_VFORKED:
+	  if (cs.report_vfork_events)
+	    {
+	      attach_child ();
+	      return wptid;
+	    }
+	  break;
+	case TARGET_WAITKIND_VFORK_DONE:
+	  if (cs.report_vfork_events)
+	    return wptid;
+	  break;
+	case TARGET_WAITKIND_EXECD:
+	  /* Always report exec events since startup relies on them.  */
+	  return wptid;
+	case TARGET_WAITKIND_SPURIOUS:
+	  /* Spurious events are unhandled by the gdbserver core.  */
+	  /* Set wptid to -1 to continue waiting from any thread.  */
+	  wptid = minus_one_ptid;
+	  break;
+	case TARGET_WAITKIND_THREAD_CREATED:
+	  if (cs.report_thread_events)
+	    return wptid;
+	  break;
+	case TARGET_WAITKIND_THREAD_EXITED:
+	{
+	  thread_info *info = find_thread_ptid (wptid);
+	  info->process ()->remove_thread (info);
+
+	  if (cs.report_thread_events)
+	    return wptid;
+
+	  /* The thread is dead so we cannot resume the the same wptid.  */
+	  wptid = ptid;
+	  break;
+	}
+	default:
+	  gdb_assert_not_reached ("Unknown stopped status");
+	}
+
+      haiku_nat_debug_printf ("Event ignored: %s",
+			      ourstatus->to_string ().c_str ());
+
+      if (haiku_nat::resume (wptid, resume_continue, 0) < 0)
+	perror_with_name (_("haiku_nat::resume"));
+    }
+}
+
+/* Implement the kill target_ops method.  */
+
+int
+haiku_process_target::kill (process_info *process)
+{
+  if (haiku_nat::kill (process->pid) < 0)
+    return -1;
+
+  mourn (process);
+  return 0;
+}
+
+/* Implement the detach target_ops method.  */
+
+int
+haiku_process_target::detach (process_info *process)
+{
+  if (haiku_nat::detach (process->pid) < 0)
+    return -1;
+
+  mourn (process);
+  return 0;
+}
+
+/* Implement the mourn target_ops method.  */
+
+void
+haiku_process_target::mourn (process_info *proc)
+{
+  proc->for_each_thread ([proc] (thread_info *thread)
+    {
+      proc->remove_thread (thread);
+    });
+
+  remove_process (proc);
+}
+
+/* Implement the join target_ops method.  */
+
+void
+haiku_process_target::join (int pid)
+{
+  gdb::waitpid (pid, nullptr, 0);
+}
+
+/* Implement the thread_alive target_ops method.  */
+
+bool
+haiku_process_target::thread_alive (ptid_t ptid)
+{
+  return haiku_nat::thread_alive (ptid);
+}
+
+/* Implement the read_memory target_ops method.  */
+
+int
+haiku_process_target::read_memory (CORE_ADDR memaddr, unsigned char *myaddr,
+				   int size)
+{
+  if (haiku_nat::read_memory (current_process ()->pid, memaddr, myaddr,
+			      &size)
+      < 0)
+    {
+      haiku_nat_debug_printf ("haiku_nat::read_memory failed: %s",
+			      safe_strerror (errno));
+      return errno;
+    }
+  return 0;
+}
+
+/* Implement the write_memory target_ops method.  */
+
+int
+haiku_process_target::write_memory (CORE_ADDR memaddr,
+				    const unsigned char *myaddr, int size)
+{
+  if (haiku_nat::write_memory (current_process ()->pid, memaddr, myaddr,
+			       &size)
+      < 0)
+    {
+      haiku_nat_debug_printf ("haiku_nat::write_memory failed: %s",
+			      safe_strerror (errno));
+      return errno;
+    }
+  return 0;
+}
+
+/* Implement the request_interrupt target_ops method.  */
+
+void
+haiku_process_target::request_interrupt ()
+{
+  thread_info *thread = get_first_thread ();
+
+  if (thread == nullptr)
+    return;
+
+  ::kill (thread->id.pid (), SIGINT);
+}
+
+/* Implement the read_offsets target_ops method.  */
+
+int
+haiku_process_target::read_offsets (CORE_ADDR *text, CORE_ADDR *data)
+{
+  if (haiku_nat::read_offsets (current_process ()->pid, text, data) < 0)
+    return 0;
+  return 1;
+}
+
+/* Implement the qxfer_osdata target_ops method.  */
+
+int
+haiku_process_target::qxfer_osdata (const char *annex, unsigned char *readbuf,
+				    unsigned const char *writebuf,
+				    CORE_ADDR offset, int len)
+{
+  if (writebuf != nullptr)
+    return -2;
+  return haiku_common_xfer_osdata (annex, readbuf, offset, len);
+}
+
+/* Implement the async target_ops method.  */
+
+bool
+haiku_process_target::async (bool enable)
+{
+  bool previous_enable = haiku_nat::is_async_p ();
+
+  if (previous_enable != enable)
+    {
+      if (enable)
+	{
+	  if (haiku_nat::async (true) < 0)
+	    warning ("haiku_nat::async failed: %s", safe_strerror (errno));
+	  else
+	    {
+	      add_file_handler (haiku_nat::async_wait_fd (),
+				handle_target_event, NULL, "haiku-low");
+	    }
+	}
+      else
+	{
+	  /* Unregister this before async_wait_fd gets invalidated.  */
+	  delete_file_handler (haiku_nat::async_wait_fd ());
+	  haiku_nat::async (false);
+	}
+    }
+
+  return previous_enable;
+}
+
+/* Implement the start_non_stop target_ops method.  */
+
+int
+haiku_process_target::start_non_stop (bool enable)
+{
+  async (enable);
+
+  if (haiku_nat::is_async_p () != enable)
+    return -1;
+
+  /* TODO: Technically we do NOT support all-stop mode, since we do not lock
+     the whole team when an event occurs. However, not accepting all-stop would
+     result in annoying messages when using the GDB frontend with default
+     configuration.  */
+  return 0;
+}
+
+/* Implement the thread_stopped target_ops method.  */
+
+bool
+haiku_process_target::thread_stopped (thread_info *thread)
+{
+  return haiku_nat::thread_stopped (thread->id);
+}
+
+/* Implement the pid_to_exec_file target_ops method.  */
+
+const char *
+haiku_process_target::pid_to_exec_file (int pid)
+{
+  return haiku_nat::pid_to_exec_file (pid);
+}
+
+/* Implement the thread_name target_ops method.  */
+
+const char *
+haiku_process_target::thread_name (ptid_t thread)
+{
+  return haiku_nat::thread_name (thread);
+}
+
+/* Report supported features.  */
+
+bool
+haiku_process_target::supports_qxfer_osdata ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_non_stop ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_multi_process ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_fork_events ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_exec_events ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_read_offsets ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_thread_stopped ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_disable_randomization ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_pid_to_exec_file ()
+{
+  return true;
+}
+
+bool
+haiku_process_target::supports_catch_syscall ()
+{
+  return true;
+}
+
+/* Supply other required functions */
+
+namespace haiku_nat
+{
+
+void
+debugger_output (const char *message)
+{
+  monitor_output (message);
+}
+
+void
+image_created (ptid_t ptid, const image_info &info)
+{
+  haiku_nat_debug_printf ("ptid=%s, name=%s, text=%p",
+			  ptid.to_string ().c_str (), info.name,
+			  (void *)info.text);
+
+  if (info.is_main_executable)
+    return;
+
+  process_info *process = find_process_pid (ptid.pid ());
+
+  if (process == nullptr)
+    return;
+
+  process->all_dlls.emplace_back (info.name, info.text);
+
+  /* DO NOT set info->dlls_changed here, since gdbserver will clobber an event.
+     Instead, do it in wait after haiku_nat::wait gives a LOADED event.  */
+}
+
+void
+image_deleted (ptid_t ptid, const image_info &info)
+{
+  haiku_nat_debug_printf ("ptid=%s, name=%s", ptid.to_string ().c_str (),
+			  info.name);
+
+  if (info.is_main_executable)
+    return;
+
+  process_info *process = find_process_pid (ptid.pid ());
+
+  if (process == nullptr)
+    return;
+
+  if (info.name == nullptr)
+    {
+      /* Delete all images.  */
+      process->all_dlls.clear ();
+    }
+  else
+    {
+      for (auto it = process->all_dlls.begin ();
+	   it != process->all_dlls.end ();)
+	{
+	  if (it->name == info.name)
+	    {
+	      auto next = std::next (it);
+	      process->all_dlls.erase (it);
+	      it = next;
+	    }
+	  else
+	    {
+	      ++it;
+	    }
+	}
+    }
+}
+
+bool
+is_catching_syscalls_for (ptid_t ptid)
+{
+  process_info *process = find_process_pid (ptid.pid ());
+
+  if (process == nullptr)
+    return false;
+
+  return !process->syscalls_to_catch.empty ();
+}
+
+}
+
+void
+initialize_low ()
+{
+  set_target_ops (the_haiku_target);
+}
diff --git a/gdbserver/haiku-low.h b/gdbserver/haiku-low.h
new file mode 100644
index 00000000..f547ff3c
--- /dev/null
+++ b/gdbserver/haiku-low.h
@@ -0,0 +1,100 @@
+/* Copyright (C) 2026 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/>.  */
+
+#ifndef GDBSERVER_HAIKU_LOW_H
+#define GDBSERVER_HAIKU_LOW_H
+
+/* Target ops definitions for a Haiku target.  */
+
+class haiku_process_target : public process_stratum_target
+{
+public:
+  int create_inferior (const char *program,
+		       const std::string &program_args) override;
+
+  void post_create_inferior () override;
+
+  int attach (unsigned long pid) override;
+
+  int kill (process_info *proc) override;
+
+  int detach (process_info *proc) override;
+
+  void mourn (process_info *proc) override;
+
+  void join (int pid) override;
+
+  bool thread_alive (ptid_t pid) override;
+
+  void resume (thread_resume *resume_info, size_t n) override;
+
+  ptid_t wait (ptid_t ptid, target_waitstatus *status,
+	       target_wait_flags options) override;
+
+  int read_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len) override;
+
+  int write_memory (CORE_ADDR memaddr, const unsigned char *myaddr,
+		    int len) override;
+
+  void request_interrupt () override;
+
+  int read_offsets (CORE_ADDR *text, CORE_ADDR *data) override;
+
+  int qxfer_osdata (const char *annex, unsigned char *readbuf,
+		    unsigned const char *writebuf, CORE_ADDR offset,
+		    int len) override;
+
+  bool async (bool enable) override;
+
+  int start_non_stop (bool enable) override;
+
+  bool thread_stopped (thread_info *thread) override;
+
+  const char *pid_to_exec_file (int pid) override;
+
+  const char *thread_name (ptid_t thread) override;
+
+  bool supports_qxfer_osdata () override;
+
+  bool supports_non_stop () override;
+
+  bool supports_multi_process () override;
+
+  bool supports_fork_events () override;
+
+  bool supports_exec_events () override;
+
+  bool supports_read_offsets () override;
+
+  bool supports_thread_stopped () override;
+
+  bool supports_disable_randomization () override;
+
+  bool supports_pid_to_exec_file () override;
+
+  bool supports_catch_syscall () override;
+
+protected:
+  /* The architecture-specific "low" methods are listed below.  */
+
+  /* Architecture-specific setup for the current process.  */
+  virtual void low_arch_setup (process_info *process = nullptr) = 0;
+};
+
+extern haiku_process_target *the_haiku_target;
+
+#endif /* GDBSERVER_HAIKU_LOW_H */
diff --git a/gdbserver/remote-utils.cc b/gdbserver/remote-utils.cc
index d7049baf..5d00df9f 100644
--- a/gdbserver/remote-utils.cc
+++ b/gdbserver/remote-utils.cc
@@ -110,6 +110,10 @@ static int listen_desc = -1;
 # define write(fd, buf, len) send (fd, (char *) buf, len, 0)
 #endif
 
+#if defined(SIGPOLL) && !defined(SIGIO)
+# define SIGIO SIGPOLL
+#endif
+
 int
 gdb_connected (void)
 {
diff --git a/gdbsupport/signals.cc b/gdbsupport/signals.cc
index 367ca571..b7e322d5 100644
--- a/gdbsupport/signals.cc
+++ b/gdbsupport/signals.cc
@@ -332,6 +332,11 @@ gdb_signal_from_host (int hostsig)
     return GDB_SIGNAL_LIBRT;
 #endif
 
+#if defined (SIGKILLTHR)
+  if (hostsig == SIGKILLTHR)
+    return GDB_SIGNAL_SIGKILLTHR;
+#endif
+
 #if defined (REALTIME_LO)
   if (hostsig >= REALTIME_LO && hostsig < REALTIME_HI)
     {
@@ -589,6 +594,11 @@ do_gdb_signal_to_host (enum gdb_signal oursig,
       return SIGLIBRT;
 #endif
 
+#if defined (SIGKILLTHR)
+    case GDB_SIGNAL_SIGKILLTHR:
+      return SIGKILLTHR;
+#endif
+
     default:
 #if defined (REALTIME_LO)
       retsig = 0;
diff --git a/include/gdb/signals.def b/include/gdb/signals.def
index c6819b0f..0d4bb011 100644
--- a/include/gdb/signals.def
+++ b/include/gdb/signals.def
@@ -196,7 +196,9 @@ SET (GDB_EXC_BREAKPOINT, 150, "EXC_BREAKPOINT", "Breakpoint")
 
 SET (GDB_SIGNAL_LIBRT, 151, "SIGLIBRT", "librt internal signal")
 
+SET (GDB_SIGNAL_SIGKILLTHR, 152, "SIGKILLTHR", "Thread killed")
+
 /* If you are adding a new signal, add it just above this comment.  */
 
 /* Last and unused enum value, for sizing arrays, etc.  */
-SET (GDB_SIGNAL_LAST, 152, NULL, "GDB_SIGNAL_LAST")
+SET (GDB_SIGNAL_LAST, 153, NULL, "GDB_SIGNAL_LAST")
-- 
2.43.0


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

* [PATCH v3 2/2] gdb: Haiku support
  2026-04-08 16:32 [PATCH v3 0/2] Support for Haiku/x86-64 in GDB Jérôme Duval
  2026-04-08 16:32 ` [PATCH v3 1/2] gdbserver: Haiku support Jérôme Duval
@ 2026-04-08 16:32 ` Jérôme Duval
  2026-04-08 16:47   ` Eli Zaretskii
  1 sibling, 1 reply; 4+ messages in thread
From: Jérôme Duval @ 2026-04-08 16:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: me, Jérôme Duval

Initial support was done by Trung Nguyen for GDB 15.1 for GSoC 2024:
See blog entries https://www.haiku-os.org/tags/gdb
Original Port repository: https://github.com/trungnt2910/gdb-haiku

I mostly adapted to the next major releases.
---
 gdb/Makefile.in             |  25 ++
 gdb/NEWS                    |   2 +
 gdb/amd64-haiku-nat.c       | 151 +++++++
 gdb/amd64-haiku-tdep.c      | 142 +++++++
 gdb/configure               |   2 +-
 gdb/configure.host          |   2 +
 gdb/configure.nat           |  15 +
 gdb/configure.tgt           |   6 +
 gdb/haiku-nat.c             | 776 ++++++++++++++++++++++++++++++++++++
 gdb/haiku-nat.h             |  75 ++++
 gdb/haiku-tdep.c            | 194 +++++++++
 gdb/haiku-tdep.h            |  44 ++
 gdb/nat/haiku-nat.h         |   6 +-
 gdb/nat/haiku-nub-message.h |   6 +-
 gdb/nat/haiku-osdata.h      |   6 +-
 gdb/solib-haiku.c           | 118 ++++++
 gdb/solib-haiku.h           |  29 ++
 gdbsupport/osabi.def        |   1 +
 18 files changed, 1590 insertions(+), 10 deletions(-)
 create mode 100644 gdb/amd64-haiku-nat.c
 create mode 100644 gdb/amd64-haiku-tdep.c
 create mode 100644 gdb/haiku-nat.c
 create mode 100644 gdb/haiku-nat.h
 create mode 100644 gdb/haiku-tdep.c
 create mode 100644 gdb/haiku-tdep.h
 create mode 100644 gdb/solib-haiku.c
 create mode 100644 gdb/solib-haiku.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 2488d789..9978b430 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -618,6 +618,22 @@ GDB_CFLAGS = \
 	-DLOCALEDIR="\"$(localedir)\"" \
 	$(DEFS)
 
+# Special rule for Haiku-specific files to avoid name clashes.
+nat/haiku-%.o: GDB_CFLAGS = \
+	-I. \
+	-I$(srcdir) \
+	-I$(srcdir)/config \
+	-DLOCALEDIR="\"$(localedir)\"" \
+	$(DEFS)
+
+# Special rule for Haiku-specific files to avoid name clashes.
+nat/haiku-%.o: GDB_CFLAGS = \
+	-I. \
+	-I$(srcdir) \
+	-I$(srcdir)/config \
+	-DLOCALEDIR="\"$(localedir)\"" \
+	$(DEFS)
+
 # MH_CFLAGS, if defined, has host-dependent CFLAGS from the config directory.
 GLOBAL_CFLAGS = $(MH_CFLAGS)
 
@@ -745,6 +761,7 @@ ALL_64_TARGET_OBS = \
 	amd64-dicos-tdep.o \
 	amd64-fbsd-tdep.o \
 	amd64-gnu-tdep.o \
+	amd64-haiku-tdep.o \
 	amd64-linux-tdep.o \
 	amd64-netbsd-tdep.o \
 	amd64-obsd-tdep.o \
@@ -828,6 +845,7 @@ ALL_TARGET_OBS = \
 	ft32-tdep.o \
 	glibc-tdep.o \
 	h8300-tdep.o \
+	haiku-tdep.o \
 	hppa-bsd-tdep.o \
 	hppa-linux-tdep.o \
 	hppa-netbsd-tdep.o \
@@ -893,6 +911,7 @@ ALL_TARGET_OBS = \
 	solib-darwin.o \
 	solib-dsbt.o \
 	solib-frv.o \
+	solib-haiku.o \
 	solib-svr4.o \
 	solib-svr4-linux.o \
 	sparc-linux-tdep.o \
@@ -1669,6 +1688,7 @@ HFILES_NO_SRCDIR = \
 	solib-dsbt.h \
 	solib-frv.h \
 	solib.h \
+	solib-haiku.h \
 	solib-svr4.h \
 	solib-svr4-linux.h \
 	solib-target.h \
@@ -1809,6 +1829,8 @@ ALLDEPFILES = \
 	amd64-fbsd-nat.c \
 	amd64-fbsd-tdep.c \
 	amd64-gnu-tdep.c \
+	amd64-haiku-nat.c \
+	amd64-haiku-tdep.c \
 	amd64-linux-nat.c \
 	amd64-linux-tdep.c \
 	amd64-nat.c \
@@ -1849,6 +1871,8 @@ ALLDEPFILES = \
 	glibc-tdep.c \
 	go32-nat.c \
 	h8300-tdep.c \
+	haiku-nat.c \
+	haiku-tdep.c \
 	hppa-bsd-tdep.c \
 	hppa-linux-nat.c \
 	hppa-linux-tdep.c \
@@ -1959,6 +1983,7 @@ ALLDEPFILES = \
 	sh-tdep.c \
 	sol2-tdep.c \
 	solib-aix.c \
+	solib-haiku.c \
 	solib-rocm.c \
 	solib-svr4.c \
 	sparc-linux-nat.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index 4cf91053..70808aa5 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -72,6 +72,8 @@ GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
 
 AArch64 MinGW                   aarch64-*-mingw*
 
+Haiku/amd64                     x86_64-*-haiku*
+
 * New commands
 
 set local-environment
diff --git a/gdb/amd64-haiku-nat.c b/gdb/amd64-haiku-nat.c
new file mode 100644
index 00000000..12820f45
--- /dev/null
+++ b/gdb/amd64-haiku-nat.c
@@ -0,0 +1,151 @@
+/* Native-dependent code for Haiku/amd64.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "defs.h"
+
+#include "amd64-tdep.h"
+#include "haiku-nat.h"
+#include "nat/haiku-nat.h"
+
+/* Very conservative inclusion of Haiku headers to prevent name clashes.  */
+typedef uint64_t uint64;
+#include <arch/x86_64/arch_debugger.h>
+
+/* At haiku_amd64_reg_offsets[REGNUM] you'll find the offset in `struct
+   debug_cpu_state' where the GDB register REGNUM is stored. */
+static constexpr auto haiku_amd64_reg_offsets = [] () constexpr {
+  std::array<int, AMD64_NUM_GREGS> result = {};
+
+  /* Set up the register offset table.  */
+#define HAIKU_DECLARE_REG_OFFSET(gdbreg, haikureg)			    \
+  result[AMD64_##gdbreg##_REGNUM]					     \
+      = offsetof (struct x86_64_debug_cpu_state, haikureg)
+
+  HAIKU_DECLARE_REG_OFFSET (RAX, rax);
+  HAIKU_DECLARE_REG_OFFSET (RBX, rbx);
+  HAIKU_DECLARE_REG_OFFSET (RCX, rcx);
+  HAIKU_DECLARE_REG_OFFSET (RDX, rdx);
+  HAIKU_DECLARE_REG_OFFSET (RSI, rsi);
+  HAIKU_DECLARE_REG_OFFSET (RDI, rdi);
+  HAIKU_DECLARE_REG_OFFSET (RBP, rbp);
+  HAIKU_DECLARE_REG_OFFSET (RSP, rsp);
+  HAIKU_DECLARE_REG_OFFSET (R8, r8);
+  HAIKU_DECLARE_REG_OFFSET (R9, r9);
+  HAIKU_DECLARE_REG_OFFSET (R10, r10);
+  HAIKU_DECLARE_REG_OFFSET (R11, r11);
+  HAIKU_DECLARE_REG_OFFSET (R12, r12);
+  HAIKU_DECLARE_REG_OFFSET (R13, r13);
+  HAIKU_DECLARE_REG_OFFSET (R14, r14);
+  HAIKU_DECLARE_REG_OFFSET (R15, r15);
+  HAIKU_DECLARE_REG_OFFSET (RIP, rip);
+  HAIKU_DECLARE_REG_OFFSET (EFLAGS, rflags);
+  HAIKU_DECLARE_REG_OFFSET (CS, cs);
+  HAIKU_DECLARE_REG_OFFSET (SS, ss);
+  HAIKU_DECLARE_REG_OFFSET (DS, ds);
+  HAIKU_DECLARE_REG_OFFSET (ES, es);
+  HAIKU_DECLARE_REG_OFFSET (FS, fs);
+  HAIKU_DECLARE_REG_OFFSET (GS, gs);
+
+#undef HAIKU_DECLARE_REG_OFFSET
+
+  return result;
+}();
+
+struct amd64_haiku_nat_target final : public haiku_nat_target
+{
+  void fetch_registers (regcache *, int) override;
+  void store_registers (regcache *, int) override;
+};
+
+void
+amd64_haiku_nat_target::fetch_registers (regcache *regcache, int regno)
+{
+  union
+  {
+    char data[sizeof (x86_64_debug_cpu_state)];
+    x86_64_debug_cpu_state state;
+  };
+
+  if (haiku_nat::get_cpu_state (regcache->ptid (), &state) < 0)
+    {
+      /* This happens when the inferior is killed by another process
+	 while being stopped. The nub port has been deleted, so we cannot
+	 send the required message to get the CPU state.  */
+      haiku_nat_debug_printf ("Failed to get actual CPU state: %s",
+			      strerror (errno));
+      memset (&state, 0, sizeof (state));
+    }
+
+  if (regno == -1)
+    {
+      for (int i = 0; i < AMD64_NUM_GREGS; ++i)
+	regcache->raw_supply (i, data + haiku_amd64_reg_offsets[i]);
+      amd64_supply_fxsave (regcache, regno, &state.extended_registers);
+    }
+  else
+    {
+      if (regno < AMD64_NUM_GREGS)
+	regcache->raw_supply (regno, data + haiku_amd64_reg_offsets[regno]);
+      else
+	amd64_supply_fxsave (regcache, regno, &state.extended_registers);
+    }
+}
+
+void
+amd64_haiku_nat_target::store_registers (regcache *regcache, int regno)
+{
+  union
+  {
+    char data[sizeof (x86_64_debug_cpu_state)];
+    x86_64_debug_cpu_state state;
+  };
+
+  if (haiku_nat::get_cpu_state (regcache->ptid (), &state) < 0)
+    {
+      haiku_nat_debug_printf ("Failed to get actual CPU state: %s",
+			      strerror (errno));
+      return;
+    }
+
+  if (regno == -1)
+    {
+      for (int i = 0; i < AMD64_NUM_GREGS; ++i)
+	regcache->raw_collect (i, data + haiku_amd64_reg_offsets[i]);
+      amd64_collect_fxsave (regcache, regno, &state.extended_registers);
+    }
+  else
+    {
+      if (regno < AMD64_NUM_GREGS)
+	regcache->raw_collect (regno, data + haiku_amd64_reg_offsets[regno]);
+      else
+	amd64_collect_fxsave (regcache, regno, &state.extended_registers);
+    }
+
+  if (haiku_nat::set_cpu_state (regcache->ptid (), &state) < 0)
+    perror_with_name (_("haiku_nat::set_cpu_state"));
+}
+
+static amd64_haiku_nat_target the_amd64_haiku_nat_target;
+
+INIT_GDB_FILE (amd64_haiku_nat)
+{
+  haiku_target = &the_amd64_haiku_nat_target;
+
+  add_inf_child_target (&the_amd64_haiku_nat_target);
+}
diff --git a/gdb/amd64-haiku-tdep.c b/gdb/amd64-haiku-tdep.c
new file mode 100644
index 00000000..ab25fb2d
--- /dev/null
+++ b/gdb/amd64-haiku-tdep.c
@@ -0,0 +1,142 @@
+/* Target-dependent code for Haiku/amd64.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "defs.h"
+
+#include "amd64-tdep.h"
+#include "extract-store-integer.h"
+#include "haiku-tdep.h"
+#include "solib.h"
+
+static int
+amd64_haiku_sigtramp_p (const frame_info_ptr &this_frame)
+{
+  CORE_ADDR pc = get_frame_pc (this_frame);
+  const char *solib_name
+      = solib_name_from_address (get_frame_program_space (this_frame), pc);
+
+  if (solib_name == nullptr || strcmp (solib_name, "commpage") != 0)
+    return false;
+
+  const char *name;
+  find_pc_partial_function (pc, &name, NULL, NULL);
+
+  if (name == nullptr || strcmp (name, "commpage_signal_handler") != 0)
+    return false;
+
+  return true;
+}
+
+/* Offset to mcontext_t in signal_frame_data,
+   from headers/private/kernel/ksignal.h.
+
+   The struct is private so it may change anytime.
+   However, the first two members of the struct are siginfo_t and ucontext_t,
+   which are public and relatively stable.  */
+#define AMD64_HAIKU_SIGNAL_FRAME_DATA_MCONTEXT_OFFSET 96
+
+static CORE_ADDR
+amd64_haiku_sigcontext_addr (const frame_info_ptr &this_frame)
+{
+  gdbarch *gdbarch = get_frame_arch (this_frame);
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  CORE_ADDR bp;
+  gdb_byte buf[8];
+
+  get_frame_register (this_frame, AMD64_RBP_REGNUM, buf);
+  bp = extract_unsigned_integer (buf, 8, byte_order);
+
+  /* Layout of the stack before function call:
+     - signal_frame_data
+     - frame->ip (8 bytes)
+     - frame->bp (8 bytes). Not written by the kernel,
+       but the signal handler has a "push %rbp" instruction.  */
+  return bp + 8 + 8 + AMD64_HAIKU_SIGNAL_FRAME_DATA_MCONTEXT_OFFSET;
+}
+
+/* From struct vregs at arch/x86_64/signal.h.  */
+static int amd64_haiku_sc_reg_offset[] = {
+  0 * 8,  /* %rax */
+  1 * 8,  /* %rbx */
+  2 * 8,  /* %rcx */
+  3 * 8,  /* %rdx */
+  5 * 8,  /* %rsi */
+  4 * 8,  /* %rdi */
+  6 * 8,  /* %rbp */
+  15 * 8, /* %rsp */
+  7 * 8,  /* %r8 */
+  8 * 8,  /* %r9 */
+  9 * 8,  /* %r10 */
+  10 * 8, /* %r11 */
+  11 * 8, /* %r12 */
+  12 * 8, /* %r13 */
+  13 * 8, /* %r14 */
+  14 * 8, /* %r15 */
+  16 * 8, /* %rip */
+  17 * 8, /* %eflags */
+
+  -1, /* %cs */
+  -1, /* %ss */
+  -1, /* %ds */
+  -1, /* %es */
+  -1, /* %fs */
+  -1  /* %gs */
+};
+
+static void
+amd64_haiku_init_abi (gdbarch_info info, gdbarch *gdbarch)
+{
+  i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
+
+  amd64_init_abi (info, gdbarch,
+		  amd64_target_description (X86_XSTATE_SSE_MASK, true));
+  haiku_init_abi (info, gdbarch);
+
+  tdep->sigtramp_p = amd64_haiku_sigtramp_p;
+  tdep->sigcontext_addr = amd64_haiku_sigcontext_addr;
+  tdep->sc_reg_offset = amd64_haiku_sc_reg_offset;
+  tdep->sc_num_regs = ARRAY_SIZE (amd64_haiku_sc_reg_offset);
+
+  /* The offset of the PC in the jmp_buf structure.
+     Found at src/system/libroot/posix/arch/x86_64/setjmp_internal.h.  */
+  tdep->jb_pc_offset = 0;
+}
+
+static enum gdb_osabi
+amd64_haiku_osabi_sniffer (bfd *abfd)
+{
+  const char *target_name = bfd_get_target (abfd);
+
+  if (strcmp (target_name, "elf64-x86-64") != 0)
+    return GDB_OSABI_UNKNOWN;
+
+  if (!haiku_check_required_symbols (abfd))
+    return GDB_OSABI_UNKNOWN;
+
+  return GDB_OSABI_HAIKU;
+}
+
+INIT_GDB_FILE (amd64_haiku_tdep)
+{
+  gdbarch_register_osabi_sniffer (bfd_arch_i386, bfd_target_elf_flavour,
+				  amd64_haiku_osabi_sniffer);
+
+  gdbarch_register_osabi (bfd_arch_i386, bfd_mach_x86_64, GDB_OSABI_HAIKU,
+			  amd64_haiku_init_abi);
+}
diff --git a/gdb/configure b/gdb/configure
index 2ff36178..f25c47b1 100755
--- a/gdb/configure
+++ b/gdb/configure
@@ -20292,7 +20292,7 @@ return socketpair ();
   return 0;
 }
 _ACEOF
-for ac_lib in '' socket; do
+for ac_lib in '' socket network; do
   if test -z "$ac_lib"; then
     ac_res="none required"
   else
diff --git a/gdb/configure.host b/gdb/configure.host
index e83b944c..be8d81af 100644
--- a/gdb/configure.host
+++ b/gdb/configure.host
@@ -114,6 +114,7 @@ i[34567]86-*-msdosdjgpp*) gdb_host=go32 ;;
 i[34567]86-*-linux*)	gdb_host=linux ;;
 i[34567]86-*-gnu*)	gdb_host=i386gnu ;;
 i[34567]86-*-openbsd*)	gdb_host=obsd ;;
+i[34567]86-*-haiku*)	gdb_host=haiku ;;
 i[34567]86-*-solaris2* | x86_64-*-solaris2*)
 			gdb_host=sol2 ;;
 i[34567]86-*-cygwin*)	gdb_host=cygwin ;;
@@ -182,6 +183,7 @@ x86_64-*-freebsd* | x86_64-*-kfreebsd*-gnu)
 x86_64-*-netbsd* | x86_64-*-knetbsd*-gnu)
 			gdb_host=nbsd64 ;;
 x86_64-*-openbsd*)	gdb_host=obsd64 ;;
+x86_64-*-haiku*)	gdb_host=haiku64 ;;
 x86_64-*-mingw*)        gdb_host=mingw64
 			gdb_host_obs=mingw-hdep.o
 			;;
diff --git a/gdb/configure.nat b/gdb/configure.nat
index 38dd4179..286d2fe7 100644
--- a/gdb/configure.nat
+++ b/gdb/configure.nat
@@ -74,6 +74,11 @@ case ${gdb_host} in
     obsd*)
 	NATDEPFILES='fork-child.o nat/fork-inferior.o inf-ptrace.o'
 	;;
+	haiku*)
+	NATDEPFILES='fork-child.o nat/fork-inferior.o \
+		nat/haiku-debug.o nat/haiku-nat.o nat/haiku-nub-message.o \
+		nat/haiku-osdata.o haiku-nat.o'
+	;;
     cygwin*)
 	NATDEPFILES='x86-nat.o nat/x86-dregs.o windows-nat.o nat/windows-nat.o'
 	;;
@@ -504,6 +509,16 @@ case ${gdb_host} in
 		;;
 	esac
 	;;
+	haiku64)
+	case ${gdb_host_cpu} in
+	    i386)
+		# Host: Haiku/amd64
+		NATDEPFILES="${NATDEPFILES} amd64-haiku-nat.o"
+		LOADLIBES='-lnetwork -lposix_error_mapper'
+		MH_CFLAGS='-DB_USE_POSITIVE_POSIX_ERRORS'
+		;;
+	esac
+	;;
     ppc64-linux)
 	case ${gdb_host_cpu} in
 	    powerpc)
diff --git a/gdb/configure.tgt b/gdb/configure.tgt
index ba418653..70c17ff4 100644
--- a/gdb/configure.tgt
+++ b/gdb/configure.tgt
@@ -128,6 +128,8 @@ case "${targ}" in
 	os_obs="netbsd-tdep.o solib-svr4.o";;
 *-*-openbsd*)
 	os_obs="obsd-tdep.o solib-svr4.o";;
+*-*-haiku*)
+	os_obs="haiku-tdep.o solib-haiku.o symfile-mem.o";;
 esac
 
 # 3. Get the rest of objects.
@@ -754,6 +756,10 @@ x86_64-*-openbsd*)
 			i386-bsd-tdep.o i386-obsd-tdep.o \
 			bsd-uthread.o"
 	;;
+x86_64-*-haiku*)
+	# Target: Haiku/amd64
+	gdb_target_obs="amd64-haiku-tdep.o ${i386_tobjs}"
+	;;
 x86_64-*-rtems*)
 	gdb_target_obs="${amd64_tobjs} ${i386_tobjs} i386-bsd-tdep.o"
 	;;
diff --git a/gdb/haiku-nat.c b/gdb/haiku-nat.c
new file mode 100644
index 00000000..74328753
--- /dev/null
+++ b/gdb/haiku-nat.c
@@ -0,0 +1,776 @@
+/* Native-dependent code for Haiku.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "defs.h"
+#include "inferior.h"
+
+#include "cli/cli-cmds.h"
+#include "exec.h"
+#include "gdb/inf-loop.h"
+#include "gdbcore.h"
+#include "gdbsupport/buildargv.h"
+#include "gdbsupport/event-loop.h"
+#include "haiku-nat.h"
+#include "nat/fork-inferior.h"
+#include "nat/haiku-nat.h"
+#include "nat/haiku-osdata.h"
+#include "objfiles.h"
+#include "observable.h"
+#include "solib.h"
+
+haiku_nat_target *haiku_target;
+
+bool debug_haiku_nat = false;
+
+static void haiku_enable_breakpoints_if_ready (inferior *inf);
+
+void
+haiku_nat_target::create_inferior (const char *exec_file,
+				   const std::string &allargs, char **env,
+				   int from_tty)
+{
+  haiku_nat_debug_printf ("exec_file=%s", exec_file);
+
+  inferior *inf = current_inferior ();
+
+  /* Do not change either targets above or the same target if already present.
+     The reason is the target stack is shared across multiple inferiors.  */
+  int ops_already_pushed = inf->target_is_pushed (this);
+
+  target_unpush_up unpusher;
+  if (!ops_already_pushed)
+    {
+      /* Clear possible core file with its process_stratum.  */
+      inf->push_target (this);
+      unpusher.reset (this);
+    }
+
+  if (disable_randomization)
+    inf->environment.set ("DISABLE_ASLR", "1");
+
+  static const auto haiku_traceme = [] () {
+    /* This happens before the child calls exec().
+       The debugger is responsible for resuming the inferior before it
+       loads the desired target.  */
+    haiku_nat::wait_for_debugger ();
+  };
+
+  static const auto haiku_init_trace = [] (int pid) {
+    haiku_nat_debug_printf ("haiku_init_trace: pid=%i", pid);
+    if (haiku_nat::attach (pid, true) < 0)
+      trace_start_error_with_name (("haiku_nat::attach"));
+
+    /* At this stage, the child is being stopped for the first debugger event.
+       It has NOT exec'ed into the desired target yet, but is still a gdbserver
+       stuck in a wait_for_debugger() call.  */
+
+    /* Consume the initial event.  */
+    target_waitstatus ourstatus;
+    if (haiku_nat::wait (ptid_t (pid), &ourstatus, 0) == minus_one_ptid)
+      perror_with_name (_("haiku_nat::wait"));
+
+    /* Allows the child to proceed to exec.  */
+    if (haiku_nat::resume (ptid_t (pid), resume_continue, 0) < 0)
+      perror_with_name (_("haiku_nat::resume"));
+  };
+
+  /* Do not use env here, the pointer might have been invalidated.  */
+  pid_t pid = fork_inferior (exec_file, allargs, inf->environment.envp (),
+			     haiku_traceme, haiku_init_trace, nullptr, nullptr,
+			     nullptr);
+
+  /* We have something that executes now.  We'll be running through
+     the shell at this point (if startup-with-shell is true), but the
+     pid shouldn't change.  */
+  thread_info *thr = add_thread_silent (this, ptid_t (pid, 0, pid));
+  switch_to_thread (thr);
+
+  unpusher.release ();
+
+  disable_breakpoints_before_startup ();
+
+  gdb_startup_inferior (pid, START_INFERIOR_TRAPS_EXPECTED);
+
+  /* Don't wait for the callbacks. Here, we know that the inferior has exec'ed
+     into the requested image. If we wait further, post_create_inferior will
+     perform lots of operations that interally triggers breakpoint_re_set,
+     which ignores the executing_startup flag.  */
+  haiku_enable_breakpoints_if_ready (inf);
+}
+
+void
+haiku_nat_target::attach (const char *args, int from_tty)
+{
+  inferior *inf = current_inferior ();
+
+  /* Do not change either targets above or the same target if already present.
+     The reason is the target stack is shared across multiple inferiors.  */
+  int ops_already_pushed = inf->target_is_pushed (this);
+
+  pid_t pid = parse_pid_to_attach (args);
+
+  if (pid == getpid ()) /* Trying to masturbate?  */
+    error (_ ("I refuse to debug myself!"));
+
+  target_unpush_up unpusher;
+  if (!ops_already_pushed)
+    {
+      /* target_pid_to_str already uses the target.  Also clear possible core
+	 file with its process_stratum.  */
+      inf->push_target (this);
+      unpusher.reset (this);
+    }
+
+  target_announce_attach (from_tty, pid);
+
+  if (haiku_nat::attach (pid, false) < 0)
+    perror_with_name (_("haiku_nat::attach"));
+
+  inferior_appeared (inf, pid);
+  inf->attach_flag = true;
+
+  const haiku_nat::team_info *team_info = haiku_nat::get_team (pid);
+  gdb_assert (team_info != nullptr);
+
+  haiku_nat::for_each_thread (pid, [&] (const haiku_nat::thread_info &info) {
+    if (info.tid == team_info->debugger_nub_thread)
+      return 0;
+
+    thread_info *thr = add_thread (this, ptid_t (pid, 0, info.tid));
+    /* Don't consider the thread stopped until we've processed its
+       initial stop.  */
+    set_internal_state (this, thr->ptid, THREAD_INT_RUNNING);
+
+    if (info.tid == info.team)
+      switch_to_thread (thr);
+
+    return 0;
+  });
+
+  gdb_assert (inferior_ptid != null_ptid);
+
+  unpusher.release ();
+}
+
+void
+haiku_nat_target::detach (inferior *inf, int from_tty)
+{
+  target_announce_detach (from_tty);
+
+  if (haiku_nat::detach (inf->pid) < 0)
+    perror ("haiku_nat::detach");
+
+  switch_to_no_thread ();
+  detach_inferior (inf);
+
+  maybe_unpush_target ();
+}
+
+void
+haiku_nat_target::resume (ptid_t ptid, int step, enum gdb_signal signal)
+{
+  if (haiku_nat::resume (ptid, step ? resume_step : resume_continue,
+			 gdb_signal_to_host (signal))
+      < 0)
+    perror_with_name (_("haiku_nat_target::resume"));
+}
+
+ptid_t
+haiku_nat_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
+			target_wait_flags target_options)
+{
+  haiku_nat_debug_printf (
+      "ptid=%s, ourstatus=%s, target_options=%i", ptid.to_string ().c_str (),
+      ourstatus->to_string ().c_str (), (int)target_options.raw ());
+
+  ptid_t wptid = haiku_nat::wait (ptid, ourstatus, target_options);
+
+  if (wptid == minus_one_ptid)
+    perror_with_name (_("haiku_nat::wait"));
+
+  if (wptid.tid () != 0 && !find_thread (wptid)
+      && ourstatus->kind () != TARGET_WAITKIND_THREAD_EXITED)
+    add_thread (this, wptid);
+
+  invalidate_target_mem_regions ();
+
+  return wptid;
+}
+
+void
+haiku_nat_target::files_info ()
+{
+  struct inferior *inf = current_inferior ();
+
+  gdb_printf (_ ("\tUsing the running image of %s %s.\n"),
+	      inf->attach_flag ? "attached" : "child",
+	      target_pid_to_str (ptid_t (inf->pid)).c_str ());
+}
+
+void
+haiku_nat_target::kill ()
+{
+  if (haiku_nat::kill (inferior_ptid.pid ()) < 0)
+    {
+      haiku_nat_debug_printf ("Failed to actually kill the process: %s",
+			      safe_strerror (errno));
+    }
+
+  target_mourn_inferior (inferior_ptid);
+}
+
+void
+haiku_nat_target::follow_exec (inferior *follow_inf, ptid_t ptid,
+			       const char *execd_pathname)
+{
+  inf_child_target::follow_exec (follow_inf, ptid, execd_pathname);
+
+  /* nat/haiku-nat.c currently does not report the EXEC event when
+     the corresponding native event is generated, but after the
+     main executable image has been loaded.
+
+     This means when the event is generated, all initial shared
+     libraries have been registered. However, GDB treats the
+     EXEC event as if the program has a clean address space and
+     nukes the solib list and loaded symbols.
+
+     We therefore call the below function to force GDB to load
+     the needed symbols again.  */
+  handle_solib_event ();
+
+  invalidate_target_mem_regions ();
+}
+
+bool
+haiku_nat_target::thread_alive (ptid_t ptid)
+{
+  return haiku_nat::thread_alive (ptid);
+}
+
+void
+haiku_nat_target::update_thread_list ()
+{
+  delete_exited_threads ();
+}
+
+std::string
+haiku_nat_target::pid_to_str (ptid_t ptid)
+{
+  return haiku_nat::pid_to_str (ptid);
+}
+
+const char *
+haiku_nat_target::thread_name (thread_info *thr)
+{
+  return haiku_nat::thread_name (thr->ptid);
+}
+
+void
+haiku_nat_target::stop (ptid_t ptid)
+{
+  if (haiku_nat::stop (ptid) < 0)
+    perror_with_name (_("haiku_nat::stop"));
+}
+
+const char *
+haiku_nat_target::pid_to_exec_file (int pid)
+{
+  return haiku_nat::pid_to_exec_file (pid);
+}
+
+bool
+haiku_nat_target::can_async_p ()
+{
+  return true;
+}
+
+bool
+haiku_nat_target::is_async_p ()
+{
+  return haiku_nat::is_async_p ();
+}
+
+void
+haiku_nat_target::async (bool enable)
+{
+  if (enable == is_async_p ())
+    return;
+
+  if (enable)
+    {
+      if (haiku_nat::async (true) < 0)
+	perror_with_name (_("haiku_nat::async"));
+      else
+	{
+	  add_file_handler (
+	      haiku_nat::async_wait_fd (),
+	      [] (int error, gdb_client_data client_data) {
+		inferior_event_handler (INF_REG_EVENT);
+	      },
+	      nullptr, "haiku-nat");
+	}
+    }
+  else
+    {
+      /* Unregister this before async_wait_fd gets invalidated.  */
+      delete_file_handler (haiku_nat::async_wait_fd ());
+      haiku_nat::async (false);
+    }
+}
+
+int
+haiku_nat_target::async_wait_fd ()
+{
+  return haiku_nat::async_wait_fd ();
+}
+
+bool
+haiku_nat_target::supports_non_stop ()
+{
+  return true;
+}
+
+bool
+haiku_nat_target::always_non_stop_p ()
+{
+  return true;
+}
+
+enum target_xfer_status
+haiku_nat_target::xfer_partial (enum target_object object, const char *annex,
+				gdb_byte *readbuf, const gdb_byte *writebuf,
+				ULONGEST offset, ULONGEST len,
+				ULONGEST *xfered_len)
+{
+  ptid_t ptid = inferior_ptid;
+
+  switch (object)
+    {
+    case TARGET_OBJECT_MEMORY:
+      {
+	int sizeLeft = std::min ((ULONGEST)INT_MAX, len);
+
+	if (writebuf != nullptr)
+	  std::ignore = haiku_nat::write_memory (
+	      ptid.pid (), (CORE_ADDR)offset, writebuf, &sizeLeft);
+	else
+	  std::ignore = haiku_nat::read_memory (ptid.pid (), (CORE_ADDR)offset,
+						readbuf, &sizeLeft);
+
+	*xfered_len = std::min ((ULONGEST)INT_MAX, len) - sizeLeft;
+
+	return (*xfered_len > 0) ? TARGET_XFER_OK : TARGET_XFER_EOF;
+      }
+      break;
+    case TARGET_OBJECT_LIBRARIES:
+      {
+	if (writebuf != nullptr)
+	  return TARGET_XFER_UNAVAILABLE;
+
+	if (current_inferior () == nullptr)
+	  return TARGET_XFER_E_IO;
+
+	std::string document = "<library-list version=\"1.0\">\n";
+	haiku_nat::for_each_image (
+	    current_inferior ()->pid, [&] (const haiku_nat::image_info &info) {
+	      if (!info.is_main_executable)
+		{
+		  document += string_printf (
+		      "  <library name=\"%s\">"
+		      "<segment address=\"%s\"/></library>\n",
+		      info.name,
+		      paddress (current_inferior ()->arch (), info.text));
+		}
+	      return 0;
+	    });
+	document += "</library-list>\n";
+
+	if (offset >= document.size ())
+	  return TARGET_XFER_EOF;
+
+	len = std::min (len, document.size () - offset);
+	memcpy (readbuf, document.c_str () + offset, len);
+
+	*xfered_len = len;
+
+	return TARGET_XFER_OK;
+      }
+      break;
+    case TARGET_OBJECT_OSDATA:
+      {
+	if (writebuf != nullptr)
+	  return TARGET_XFER_UNAVAILABLE;
+
+	*xfered_len = haiku_common_xfer_osdata (annex, readbuf, offset, len);
+
+	return (*xfered_len > 0) ? TARGET_XFER_OK : TARGET_XFER_EOF;
+      }
+    default:
+      haiku_nat_debug_printf ("Unimplemented xfer object: %i", (int)object);
+    }
+
+  return inf_child_target::xfer_partial (object, annex, readbuf, writebuf,
+					 offset, len, xfered_len);
+}
+
+std::vector<mem_region>
+haiku_nat_target::memory_map ()
+{
+  std::vector<mem_region> result;
+
+  haiku_nat::for_each_area (
+      current_inferior ()->pid, [&] (const haiku_nat::area_info &info) {
+	/* While some regions appear read-only to the user,
+	   as the debugger, we can write anywhere.
+
+	   If this is set otherwise, software breakpoints in read-only
+	   regions (such as shared libraries) will not work.  */
+	result.emplace_back (info.address, info.address + info.size, MEM_RW);
+	return 0;
+      });
+
+  return result;
+}
+
+bool
+haiku_nat_target::supports_multi_process ()
+{
+  return true;
+}
+
+bool
+haiku_nat_target::supports_disable_randomization ()
+{
+  return true;
+}
+
+bool
+haiku_nat_target::info_proc (const char *args, enum info_proc_what what)
+{
+  pid_t pid;
+  bool do_cmdline = false;
+  bool do_exe = false;
+  bool do_mappings = false;
+  bool do_status = false;
+
+  switch (what)
+    {
+    case IP_MINIMAL:
+      do_cmdline = true;
+      do_exe = true;
+      break;
+    case IP_STAT:
+    case IP_STATUS:
+      do_status = true;
+      break;
+    case IP_MAPPINGS:
+      do_mappings = true;
+      break;
+    case IP_CMDLINE:
+      do_cmdline = true;
+      break;
+    case IP_EXE:
+      do_exe = true;
+      break;
+    case IP_CWD:
+      /* There is no obvious method of getting the CWD of a different team.
+	 _kern_get_extended_team_info might provide what we want, but the
+	 syscall stores the result in a private class "KMessage" instead of
+	 normal structs.  */
+      return false;
+    case IP_ALL:
+      do_cmdline = true;
+      do_exe = true;
+      do_mappings = true;
+      do_status = true;
+      break;
+    default:
+      error (_ ("Not supported on this target."));
+    }
+
+  gdb_argv built_argv (args);
+  if (built_argv.count () == 0)
+    {
+      pid = inferior_ptid.pid ();
+      if (pid == 0)
+	error (_ ("No current team: you must name one."));
+    }
+  else if (built_argv.count () == 1 && isdigit (built_argv[0][0]))
+    pid = strtol (built_argv[0], NULL, 10);
+  else
+    error (_ ("Invalid arguments."));
+
+  gdb_printf (_ ("team %d\n"), pid);
+
+  const haiku_nat::team_info *info = nullptr;
+
+  if (do_cmdline || do_status)
+    info = haiku_nat::get_team (pid);
+
+  if (do_cmdline)
+    {
+      if (info != nullptr)
+	gdb_printf ("cmdline = '%s'\n", info->args);
+      else
+	warning (_ ("unable to fetch command line"));
+    }
+
+  if (do_exe)
+    {
+      const char *exe = pid_to_exec_file (pid);
+      if (exe != nullptr)
+	gdb_printf ("exe = '%s'\n", exe);
+      else
+	warning (_ ("unable to fetch executable path name"));
+    }
+
+  if (do_mappings)
+    {
+      bool first = true;
+      if (haiku_nat::for_each_area (
+	      pid,
+	      [&] (const haiku_nat::area_info &area_info) {
+		if (first)
+		  {
+		    gdb_printf (_ ("Mapped areas:\n\n"));
+		    gdb_printf ("%6s %18s %10s %10s %6s %6s %5s %5s %s\n",
+				"ID", "address", "size", "alloc.", "prot",
+				"#-cow", "#-in", "#-out", "name");
+		    first = false;
+		  }
+
+		std::string prot;
+		if (area_info.can_read)
+		  prot += "r";
+		if (area_info.can_write)
+		  prot += "w";
+		if (area_info.can_exec)
+		  prot += "x";
+		if (area_info.is_stack)
+		  prot += "s";
+		if (area_info.can_clone)
+		  prot += "c";
+
+		gdb_printf ("%6s %18s %10s %10s %6s %6s %5s %5s %s\n",
+			    plongest (area_info.id),
+			    core_addr_to_string (area_info.address),
+			    phex_nz (area_info.size, 0),
+			    phex_nz (area_info.ram_size, 0), prot.c_str (),
+			    pulongest (area_info.copy_count),
+			    pulongest (area_info.in_count),
+			    pulongest (area_info.out_count), area_info.name);
+
+		return 0;
+	      })
+	  < 0)
+	{
+	  warning (_ ("unable to fetch virtual memory map"));
+	}
+    }
+
+  if (do_status)
+    {
+      if (info != nullptr)
+	{
+	  gdb_printf ("Name: %s\n", info->name);
+	  gdb_printf ("Parent team: %s\n", plongest (info->parent));
+	  gdb_printf ("Process group: %s\n", plongest (info->group_id));
+	  gdb_printf ("Session id: %s\n", plongest (info->session_id));
+	  gdb_printf ("User IDs (real, effective): %s %s\n",
+		      plongest (info->real_uid), plongest (info->uid));
+	  gdb_printf ("Group IDs (real, effective): %s %s\n",
+		      plongest (info->real_gid), plongest (info->gid));
+	  gdb_printf ("Thread count: %s\n", pulongest (info->thread_count));
+	  gdb_printf ("Image count: %s\n", pulongest (info->image_count));
+	  gdb_printf ("Area count: %s\n", pulongest (info->area_count));
+	  gdb_printf ("Debugger nub thread: %s\n",
+		      plongest (info->debugger_nub_thread));
+	  gdb_printf ("Debugger nub port: %s\n",
+		      plongest (info->debugger_nub_port));
+	}
+      else
+	warning (_ ("unable to fetch team information"));
+    }
+
+  return true;
+}
+
+/* Utilities.  */
+
+static void
+haiku_relocate_main_executable (inferior *inf)
+{
+  CORE_ADDR text;
+  CORE_ADDR data;
+
+  if (haiku_nat::read_offsets (inf->pid, &text, &data) < 0)
+    return;
+
+  CORE_ADDR displacement = text;
+
+  if (inf->pspace->exec_bfd ())
+    {
+      asection *asect;
+
+      bfd *exec_bfd = inf->pspace->exec_bfd ();
+      for (asect = exec_bfd->sections; asect != NULL; asect = asect->next)
+	exec_set_section_address (bfd_get_filename (exec_bfd), asect->index,
+				  bfd_section_vma (asect) + displacement);
+    }
+
+  if (inf->pspace->symfile_object_file == nullptr)
+    symbol_file_add_main (inf->pspace->exec_filename (),
+			  SYMFILE_DEFER_BP_RESET);
+
+  objfile *objf = inf->pspace->symfile_object_file;
+  /* The call above should ensure that this is filled in.  */
+  gdb_assert (objf != nullptr);
+  objfile_rebase (objf, displacement);
+
+  haiku_nat_debug_printf ("rebased: %s", core_addr_to_string (displacement));
+}
+
+static void
+haiku_enable_breakpoints_if_ready (inferior *inf)
+{
+  if (strcmp (haiku_nat::pid_to_exec_file (inf->pid),
+	      inf->pspace->exec_filename ())
+      != 0)
+    {
+      /* Not ready yet. The inferior is still executing a wrapper
+	 (usually bash).  */
+      return;
+    }
+
+  /* Refresh the regions so that write operations can be done correctly.  */
+  invalidate_target_mem_regions ();
+
+  /* We can get correct offsets and relocate now.  */
+  haiku_relocate_main_executable (inf);
+
+  enable_breakpoints_after_startup ();
+}
+
+/* Supply other required functions.  */
+
+namespace haiku_nat
+{
+
+void
+debugger_output (const char *message)
+{
+  gdb_printf ("%s\n", message);
+}
+
+void
+image_created (ptid_t ptid, const image_info &info)
+{
+  haiku_nat_debug_printf ("ptid=%s, name=%s, text=%p",
+			  ptid.to_string ().c_str (), info.name,
+			  (void *)info.text);
+
+  /* To be handled by solib-haiku.c.  */
+}
+
+void
+image_deleted (ptid_t ptid, const image_info &info)
+{
+  haiku_nat_debug_printf ("ptid=%s, name=%s", ptid.to_string ().c_str (),
+			  info.name);
+
+  if (info.is_main_executable)
+    {
+      /* This means all images have been deleted. This usually signals that
+	 the Haiku team just called exec.
+
+	 We want to disable breakpoints for now to prevent those pointing to
+	 the main executable from causing issues with unrelocated addresses.
+	 Then, after the creation or exec call completes and the new inferior
+	 gets finalized, we can relocate and enable these breakpoints again.
+
+	 We also cannot disable the breakpoints later than this. After the
+	 event, images for the new executable starts loading. Disabling the
+	 breakpoints causes GDB to write bogus data back to the fresh
+	 binaries.  */
+
+      disable_breakpoints_before_startup ();
+      invalidate_target_mem_regions ();
+    }
+
+  /* The rest to be handled by solib-haiku.c.  */
+}
+
+bool
+is_catching_syscalls_for (ptid_t ptid)
+{
+  inferior *inf = find_inferior_ptid (haiku_target, ptid);
+  if (inf == nullptr)
+    return false;
+
+  std::optional<scoped_restore_current_thread> maybe_restore_thread
+      = maybe_switch_inferior (inf);
+
+  return catch_syscall_enabled () > 0;
+}
+
+}
+
+/* Initialization.  */
+
+INIT_GDB_FILE (haiku_nat)
+{
+  /* We cannot do this in target_op's own callbacks, since they are called too
+     early after attaching or an exec event. At that point, symfile_object_file
+     remains invalid.
+
+     Previous ports put this in Haiku's solib_create_inferior_hook callback.
+     However, this callback is also shared by remote targets and therefore
+     assumes gathering information from the address space instead of the host
+     OS, which is what haiku_nat::read_offsets does under the hood. With the
+     old implementation, GDB connected to gdbserver debugging PID X on the
+     target would attempt to use haiku_nat::read_offsets on the same PID X
+     on the local machine - this is undesired.  */
+
+  gdb::observers::inferior_created.attach (
+      [] (inferior *inf) {
+	if (inf->target_is_pushed (haiku_target))
+	  haiku_enable_breakpoints_if_ready (inf);
+      },
+      "haiku");
+
+  gdb::observers::inferior_execd.attach (
+      [] (inferior *exec, inferior *foll) {
+	if (foll->target_is_pushed (haiku_target))
+	  haiku_enable_breakpoints_if_ready (foll);
+      },
+      "haiku");
+
+  add_setshow_boolean_cmd (
+      "haiku-nat", class_maintenance, &debug_haiku_nat,
+      _ ("Set debugging of Haiku native target."),
+      _ ("Show debugging of Haiku native target."), _ ("\
+When on, print debug messages relating to the Haiku native target."),
+      nullptr,
+      [] (struct ui_file *file, int from_tty, struct cmd_list_element *c,
+	  const char *value) {
+	gdb_printf (file, _ ("Debugging of Haiku native targets is %s.\n"),
+		    value);
+      },
+      &setdebuglist, &showdebuglist);
+}
diff --git a/gdb/haiku-nat.h b/gdb/haiku-nat.h
new file mode 100644
index 00000000..01e2e6de
--- /dev/null
+++ b/gdb/haiku-nat.h
@@ -0,0 +1,75 @@
+/* Native-dependent code for Haiku.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "inf-child.h"
+
+/* A prototype Haiku target.  */
+
+struct haiku_nat_target : public inf_child_target
+{
+  void create_inferior (const char *, const std::string &, char **,
+			int) override;
+
+  void attach (const char *, int) override;
+
+  void detach (inferior *, int) override;
+
+  void resume (ptid_t, int, enum gdb_signal) override;
+
+  ptid_t wait (ptid_t, target_waitstatus *, target_wait_flags) override;
+
+  void files_info () override;
+
+  void kill () override;
+
+  void follow_exec (inferior *, ptid_t, const char *) override;
+
+  bool thread_alive (ptid_t) override;
+  void update_thread_list () override;
+  std::string pid_to_str (ptid_t) override;
+
+  const char *thread_name (thread_info *) override;
+
+  void stop (ptid_t) override;
+
+  const char *pid_to_exec_file (int) override;
+
+  bool can_async_p () override;
+  bool is_async_p () override;
+  void async (bool) override;
+  int async_wait_fd () override;
+
+  bool supports_non_stop () override;
+  bool always_non_stop_p () override;
+
+  enum target_xfer_status xfer_partial (enum target_object, const char *,
+					gdb_byte *, const gdb_byte *, ULONGEST,
+					ULONGEST, ULONGEST *) override;
+
+  std::vector<mem_region> memory_map () override;
+
+  bool supports_multi_process () override;
+
+  bool supports_disable_randomization () override;
+
+  bool info_proc (const char *, enum info_proc_what) override;
+};
+
+/* The final/concrete instance.  */
+extern haiku_nat_target *haiku_target;
diff --git a/gdb/haiku-tdep.c b/gdb/haiku-tdep.c
new file mode 100644
index 00000000..8985d416
--- /dev/null
+++ b/gdb/haiku-tdep.c
@@ -0,0 +1,194 @@
+/* Common target-dependent code for Haiku systems.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "defs.h"
+
+#include "bfd.h"
+#include "elf-bfd.h"
+#include "gdbarch.h"
+#include "gdbcore.h"
+#include "haiku-tdep.h"
+#include "inferior.h"
+#include "osdata.h"
+#include "solib-haiku.h"
+
+/* See haiku-tdep.h.  */
+
+void
+haiku_init_abi (gdbarch_info info, gdbarch *gdbarch)
+{
+  set_gdbarch_make_solib_ops (gdbarch, make_haiku_solib_ops);
+}
+
+/* See haiku-tdep.h.  */
+
+bool
+haiku_check_required_symbols (bfd *abfd)
+{
+  long storage_needed = bfd_get_symtab_upper_bound (abfd);
+  if (storage_needed <= 0)
+    return false;
+
+  gdb::unique_xmalloc_ptr<asymbol *> symbol_table (
+      (asymbol **)xmalloc (storage_needed));
+  long number_of_symbols = bfd_canonicalize_symtab (abfd, symbol_table.get ());
+
+  if (number_of_symbols <= 0)
+    return false;
+
+  for (long i = 0; i < number_of_symbols; ++i)
+    {
+      const char *name = bfd_asymbol_name (symbol_table.get ()[i]);
+
+      if (strcmp (name, "_gSharedObjectHaikuVersion") == 0)
+	return true;
+    }
+
+  return false;
+}
+
+/* See haiku-tdep.h.  */
+
+gdb_bfd_ref_ptr
+haiku_bfd_open_commpage ()
+{
+  /* Get any valid BFD object as a template.
+     Otherwise, GDB will complain with a segfault.  */
+  bfd *tmpbfd = current_inferior ()->pspace->exec_bfd ();
+  if (tmpbfd == nullptr)
+    tmpbfd = get_inferior_core_bfd (current_inferior ());
+  if (tmpbfd == nullptr)
+    return nullptr;
+
+  /* Create a hollow BFD object.  */
+  bfd *nbfd = bfd_create ("commpage", tmpbfd);
+  if (nbfd == nullptr)
+    return nullptr;
+
+  /* Close in case of failure.  */
+  std::unique_ptr<bfd, decltype (&bfd_close)> bfd_deleter (nbfd, bfd_close);
+
+  /* Prepare the BFD for writing.  */
+  if (!bfd_make_writable (nbfd))
+    return nullptr;
+
+  asection *section = bfd_make_section (nbfd, ".text");
+  section->size = HAIKU_COMMPAGE_SIZE;
+
+  /* Read the commpage symbols from the target.  */
+  std::unique_ptr<osdata> comm_data = get_osdata ("comm");
+  gdb_assert (comm_data->type == "comm");
+
+  size_t sym_count = comm_data->items.size ();
+
+  asymbol **symtab
+      = (asymbol **)bfd_alloc (nbfd, (sym_count + 1) * sizeof (asymbol *));
+
+  for (size_t i = 0; i < sym_count; ++i)
+    {
+      elf_symbol_type *sym = (elf_symbol_type *)bfd_make_empty_symbol (nbfd);
+      sym->symbol.section = section;
+
+      for (const auto &[name, value] : comm_data->items[i].columns)
+	{
+	  if (name == "name")
+	    {
+	      char *tmp = (char *)bfd_alloc (nbfd, value.size () + 1);
+	      memcpy (tmp, value.c_str (), value.size () + 1);
+	      bfd_set_asymbol_name (&sym->symbol, tmp);
+	    }
+	  else if (name == "value")
+	    {
+	      sym->symbol.value = strtoulst (value.c_str (), nullptr, 10);
+	      sym->internal_elf_sym.st_value = sym->symbol.value;
+	    }
+	  else if (name == "size")
+	    {
+	      sym->internal_elf_sym.st_size
+		  = strtoulst (value.c_str (), nullptr, 10);
+	    }
+	  else if (name == "type")
+	    {
+	      sym->symbol.flags = BSF_GLOBAL;
+	      for (char flag : value)
+		{
+		  switch (flag)
+		    {
+		    case 'f':
+		      sym->symbol.flags |= BSF_FUNCTION;
+		      break;
+		    case 'o':
+		      sym->symbol.flags |= BSF_OBJECT;
+		      break;
+		    }
+		}
+	    }
+	}
+
+      symtab[i] = (asymbol *)sym;
+    }
+
+  symtab[sym_count] = nullptr;
+
+  /* Write the symbol table.  */
+  if (!bfd_set_symtab (nbfd, symtab, sym_count))
+    return nullptr;
+
+  /* Prepare the BFD for reading by GDB.  */
+  if (!bfd_make_readable (nbfd))
+    return nullptr;
+
+  bfd_deleter.release ();
+
+  return gdb_bfd_ref_ptr::new_reference (nbfd);
+}
+
+/* See haiku-tdep.h.  */
+
+CORE_ADDR
+haiku_get_commpage_address ()
+{
+  /* Read the images from the target.  */
+  std::unique_ptr<osdata> images = get_osdata ("images");
+  gdb_assert (images->type == "images");
+
+  std::string current_team = std::to_string (current_inferior ()->pid);
+
+  for (const auto &item : images->items)
+    {
+      bool matches_team = false;
+      bool matches_name = false;
+      const char *text_value = nullptr;
+
+      for (const auto &[name, value] : item.columns)
+	{
+	  if (name == "team")
+	    matches_team = value == current_team;
+	  else if (name == "name")
+	    matches_name = value == "commpage";
+	  else if (name == "text")
+	    text_value = value.c_str ();
+	}
+
+      if (matches_team && matches_name && text_value != nullptr)
+	return string_to_core_addr (text_value);
+    }
+
+  return 0;
+}
diff --git a/gdb/haiku-tdep.h b/gdb/haiku-tdep.h
new file mode 100644
index 00000000..58b3a09c
--- /dev/null
+++ b/gdb/haiku-tdep.h
@@ -0,0 +1,44 @@
+/* Common target-dependent definitions for Haiku systems.
+
+   Copyright (C) 2026 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/>.  */
+
+#ifndef GDB_HAIKU_TDEP_H
+#define GDB_HAIKU_TDEP_H
+
+#include "gdb_bfd.h"
+
+/* Derived from headers/private/system/commpage_defs.h.  */
+#define HAIKU_COMMPAGE_SIZE (0x8000)
+
+/* Haiku specific set of ABI-related routines.  */
+
+void haiku_init_abi (struct gdbarch_info, gdbarch *);
+
+/* Used by OS ABI sniffers to check for Haiku-specific symbols.  */
+
+bool haiku_check_required_symbols (bfd *);
+
+/* Opens the virtual commpage image.  */
+
+gdb_bfd_ref_ptr haiku_bfd_open_commpage ();
+
+/* Gets the commpage address from the target.  */
+
+CORE_ADDR haiku_get_commpage_address ();
+
+#endif /* GDB_HAIKU_TDEP_H */
diff --git a/gdb/nat/haiku-nat.h b/gdb/nat/haiku-nat.h
index 0825be9d..0838064a 100644
--- a/gdb/nat/haiku-nat.h
+++ b/gdb/nat/haiku-nat.h
@@ -17,8 +17,8 @@
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-#ifndef NAT_HAIKU_NAT_H
-#define NAT_HAIKU_NAT_H
+#ifndef GDB_NAT_HAIKU_NAT_H
+#define GDB_NAT_HAIKU_NAT_H
 
 #include <functional>
 
@@ -426,4 +426,4 @@ extern bool debug_haiku_nat;
 #define HAIKU_NAT_SCOPED_DEBUG_ENTER_EXIT				     \
   scoped_debug_enter_exit (debug_haiku_nat, "haiku-nat")
 
-#endif /* NAT_HAIKU_NAT_H */
+#endif /* GDB_NAT_HAIKU_NAT_H */
diff --git a/gdb/nat/haiku-nub-message.h b/gdb/nat/haiku-nub-message.h
index 5fdc409b..eb4cefcf 100644
--- a/gdb/nat/haiku-nub-message.h
+++ b/gdb/nat/haiku-nub-message.h
@@ -17,8 +17,8 @@
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-#ifndef NAT_HAIKU_NUB_MESSAGE_H
-#define NAT_HAIKU_NUB_MESSAGE_H
+#ifndef GDB_NAT_HAIKU_NUB_MESSAGE_H
+#define GDB_NAT_HAIKU_NUB_MESSAGE_H
 
 #include "gnulib/config.h"
 
@@ -138,4 +138,4 @@ haiku_send_nub_message (port_id nub_port,
   return (result < B_OK) ? result : reply.error;
 }
 
-#endif /* NAT_HAIKU_NUB_MESSAGE_H */
+#endif /* GDB_NAT_HAIKU_NUB_MESSAGE_H */
diff --git a/gdb/nat/haiku-osdata.h b/gdb/nat/haiku-osdata.h
index 176814e7..7ead2ca8 100644
--- a/gdb/nat/haiku-osdata.h
+++ b/gdb/nat/haiku-osdata.h
@@ -17,10 +17,10 @@
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-#ifndef NAT_HAIKU_OSDATA_H
-#define NAT_HAIKU_OSDATA_H
+#ifndef GDB_NAT_HAIKU_OSDATA_H
+#define GDB_NAT_HAIKU_OSDATA_H
 
 extern LONGEST haiku_common_xfer_osdata (const char *annex, gdb_byte *readbuf,
 					 ULONGEST offset, ULONGEST len);
 
-#endif /* NAT_HAIKU_OSDATA_H */
+#endif /* GDB_NAT_HAIKU_OSDATA_H */
diff --git a/gdb/solib-haiku.c b/gdb/solib-haiku.c
new file mode 100644
index 00000000..31de8158
--- /dev/null
+++ b/gdb/solib-haiku.c
@@ -0,0 +1,118 @@
+/* Handle shared libraries for GDB, the GNU Debugger.
+
+   Copyright (C) 2026 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/>.  */
+
+#include "defs.h"
+
+#include "exec.h"
+#include "haiku-tdep.h"
+#include "inferior.h"
+#include "objfiles.h"
+#include "solib.h"
+#include "solib-haiku.h"
+#include "solib-target.h"
+
+
+/* solib_ops for Haiku systems.  */
+
+struct haiku_solib_ops : public target_solib_ops
+{
+  using target_solib_ops::target_solib_ops;
+
+  void relocate_section_addresses (solib &so, target_section *) const override;
+  void clear_solib (program_space *pspace) const override;
+  void create_inferior_hook (int from_tty) const override;
+  owning_intrusive_list<solib> current_sos () const override;
+  bool open_symbol_file_object (int from_tty) const override;
+  bool in_dynsym_resolve_code (CORE_ADDR pc) const override;
+  gdb_bfd_ref_ptr bfd_open (const char *pathname) const override;
+};
+
+/* See solib-haiku.h.  */
+
+solib_ops_up
+make_haiku_solib_ops (program_space *pspace)
+{
+  return std::make_unique<haiku_solib_ops> (pspace);
+}
+
+
+/* For other targets, the solib implementation usually reads hints from the
+   dynamic linker in the active address space, which could be anything from a
+   core file to a live inferior.
+
+   Haiku's runtime_loader does not export such information. The nearest
+   we have is the static variable sLoadedImages. We therefore have to rely on
+   what the target reports.
+
+   This is basically a wrapper around solib-target.c.  */
+
+void
+haiku_solib_ops::relocate_section_addresses (solib &so,
+					     target_section *sec) const
+{
+  if (so.name == "commpage")
+  {
+    CORE_ADDR commpage_address = haiku_get_commpage_address ();
+    sec->addr = commpage_address;
+    sec->endaddr = commpage_address + HAIKU_COMMPAGE_SIZE;
+
+    so.addr_low = commpage_address;
+    so.addr_high = commpage_address + HAIKU_COMMPAGE_SIZE;
+  }
+}
+
+void
+haiku_solib_ops::clear_solib (program_space *pspace) const
+{
+  target_solib_ops::clear_solib (pspace);
+}
+
+void
+haiku_solib_ops::create_inferior_hook (int from_tty) const
+{
+  target_solib_ops::create_inferior_hook (from_tty);
+}
+
+owning_intrusive_list<solib>
+haiku_solib_ops::current_sos () const
+{
+  return target_solib_ops::current_sos ();
+}
+
+bool
+haiku_solib_ops::open_symbol_file_object (int from_tty) const
+{
+  return target_solib_ops::open_symbol_file_object (from_tty);
+}
+
+bool
+haiku_solib_ops::in_dynsym_resolve_code (CORE_ADDR pc) const
+{
+  /* No dynamic resolving implemented in Haiku yet.
+     Return what the generic code has to say.  */
+  return target_solib_ops::in_dynsym_resolve_code (pc);
+}
+
+gdb_bfd_ref_ptr
+haiku_solib_ops::bfd_open (const char *pathname) const
+{
+  if (strcmp (pathname, "commpage") == 0)
+    return haiku_bfd_open_commpage ();
+  return target_solib_ops::bfd_open (pathname);
+}
diff --git a/gdb/solib-haiku.h b/gdb/solib-haiku.h
new file mode 100644
index 00000000..d8786a83
--- /dev/null
+++ b/gdb/solib-haiku.h
@@ -0,0 +1,29 @@
+/* Handle shared libraries for GDB, the GNU Debugger.
+
+   Copyright (C) 2026 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/>.  */
+
+#ifndef GDB_SOLIB_HAIKU_H
+#define GDB_SOLIB_HAIKU_H
+
+#include "solib.h"
+
+/* Return a new solib_ops for Haiku systems.  */
+
+extern solib_ops_up make_haiku_solib_ops (program_space *pspace);
+
+#endif /* GDB_SOLIB_HAIKU_H */
diff --git a/gdbsupport/osabi.def b/gdbsupport/osabi.def
index 230c21f0..41d3bb6e 100644
--- a/gdbsupport/osabi.def
+++ b/gdbsupport/osabi.def
@@ -41,6 +41,7 @@ GDB_OSABI_DEF (LINUX, "GNU/Linux", "linux(-gnu[^-]*)?")
 GDB_OSABI_DEF (FREEBSD, "FreeBSD", nullptr)
 GDB_OSABI_DEF (NETBSD, "NetBSD", nullptr)
 GDB_OSABI_DEF (OPENBSD, "OpenBSD", nullptr)
+GDB_OSABI_DEF (HAIKU, "Haiku", nullptr)
 GDB_OSABI_DEF (WINCE, "WindowsCE", nullptr)
 GDB_OSABI_DEF (GO32, "DJGPP", nullptr)
 GDB_OSABI_DEF (CYGWIN, "Cygwin", nullptr)
-- 
2.43.0


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

* Re: [PATCH v3 2/2] gdb: Haiku support
  2026-04-08 16:32 ` [PATCH v3 2/2] gdb: " Jérôme Duval
@ 2026-04-08 16:47   ` Eli Zaretskii
  0 siblings, 0 replies; 4+ messages in thread
From: Eli Zaretskii @ 2026-04-08 16:47 UTC (permalink / raw)
  To: Jérôme Duval; +Cc: gdb-patches, me

> From: Jérôme Duval <jerome.duval@gmail.com>
> Cc: me@trungnt2910.com,
>  Jérôme Duval <jerome.duval@gmail.com>
> Date: Wed,  8 Apr 2026 18:32:08 +0200
> 
> Initial support was done by Trung Nguyen for GDB 15.1 for GSoC 2024:
> See blog entries https://www.haiku-os.org/tags/gdb
> Original Port repository: https://github.com/trungnt2910/gdb-haiku
> 
> I mostly adapted to the next major releases.
> ---
>  gdb/Makefile.in             |  25 ++
>  gdb/NEWS                    |   2 +
>  gdb/amd64-haiku-nat.c       | 151 +++++++
>  gdb/amd64-haiku-tdep.c      | 142 +++++++
>  gdb/configure               |   2 +-
>  gdb/configure.host          |   2 +
>  gdb/configure.nat           |  15 +
>  gdb/configure.tgt           |   6 +
>  gdb/haiku-nat.c             | 776 ++++++++++++++++++++++++++++++++++++
>  gdb/haiku-nat.h             |  75 ++++
>  gdb/haiku-tdep.c            | 194 +++++++++
>  gdb/haiku-tdep.h            |  44 ++
>  gdb/nat/haiku-nat.h         |   6 +-
>  gdb/nat/haiku-nub-message.h |   6 +-
>  gdb/nat/haiku-osdata.h      |   6 +-
>  gdb/solib-haiku.c           | 118 ++++++
>  gdb/solib-haiku.h           |  29 ++
>  gdbsupport/osabi.def        |   1 +
>  18 files changed, 1590 insertions(+), 10 deletions(-)
>  create mode 100644 gdb/amd64-haiku-nat.c
>  create mode 100644 gdb/amd64-haiku-tdep.c
>  create mode 100644 gdb/haiku-nat.c
>  create mode 100644 gdb/haiku-nat.h
>  create mode 100644 gdb/haiku-tdep.c
>  create mode 100644 gdb/haiku-tdep.h
>  create mode 100644 gdb/solib-haiku.c
>  create mode 100644 gdb/solib-haiku.h

Thanks, the NEWS part is approved.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

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

end of thread, other threads:[~2026-04-08 16:47 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-08 16:32 [PATCH v3 0/2] Support for Haiku/x86-64 in GDB Jérôme Duval
2026-04-08 16:32 ` [PATCH v3 1/2] gdbserver: Haiku support Jérôme Duval
2026-04-08 16:32 ` [PATCH v3 2/2] gdb: " Jérôme Duval
2026-04-08 16:47   ` Eli Zaretskii

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox