From patchwork Wed Apr 8 16:32:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?b?SsOpcsO0bWUgRHV2YWw=?= X-Patchwork-Id: 132821 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id AE5B84BA2E2D for ; Wed, 8 Apr 2026 16:33:29 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org AE5B84BA2E2D Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=IpyvXVAW X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mail-wr1-x42a.google.com (mail-wr1-x42a.google.com [IPv6:2a00:1450:4864:20::42a]) by sourceware.org (Postfix) with ESMTPS id EA1154BA2E04 for ; Wed, 8 Apr 2026 16:32:29 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org EA1154BA2E04 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org EA1154BA2E04 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::42a ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1775665950; cv=none; b=br934oSENnfPXjYL/Pat7bfLO+qSdZORZ2ADLfZYaTxHgZIgXk30/Csg2d9yGjJ4KiPdnJ8uttThhHsgy4lpkuSCWgX8a7PSJh5xogAlEi7r5V7L5ryyjAkiJODHopUrQ1RKdnJO1h0HH0+QPWhr9hvUX41c9hjiRkcVbNL/6Q8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1775665950; c=relaxed/simple; bh=bwNlgaNmIon6rI/JiXHar2MNDqzrslqO1If2zPrp8OA=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=IeM+wwimDwnMsNHorZUy3EOx0SqpVbh7hVxePwFyY2Bta0OphWgq1TK4GWo+kajQ4KrBFEpvq1ISsrgYf6K93A4J+ioYHLaaBZiMZ5kKnDtiLJ+dO0R/YaFdxXt9B+NAnU5UnBkmpdaMPs0GRQGwPi4qd5y1ZWoXNoXb36X4+fM= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org EA1154BA2E04 Received: by mail-wr1-x42a.google.com with SMTP id ffacd0b85a97d-43cfbd17589so18103f8f.0 for ; Wed, 08 Apr 2026 09:32:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775665948; x=1776270748; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=j5oN17UzjxUEYZ10CnqEAUHX7p+VTqlz/w9T9fyagyw=; b=IpyvXVAWOuI74LDDHa8KraO7nEQxLh6ELLHAIzUQUAgQTYRLwo5kLn2pVWb+A/Jsq4 vlyWHmiCCJjaBcn+3MpyKYO+XCfx7Bm8O+SgY71yP1gTk0ApDTHPukyWIokudY61/lkv 1d6Q1DGbYcpDY0gcRxGzYS9mD/a1PYTd0iulaGoExfSEpQlJI6H+tTOQ0lJm+F2EL/UX 3QUjxTkDFk95YUM5QW5gHEB5lxjeFuFRlLmbGO4BEzyLMyRqvLFYDM/vYhEHEsc6WezR Rfcq9YMa93gXcpYYJIfai3m2vbCOPGyBTLjbJWaO+01wsJZrG0sRKv4wzzyFMp2yZOMn rIMw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775665948; x=1776270748; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=j5oN17UzjxUEYZ10CnqEAUHX7p+VTqlz/w9T9fyagyw=; b=plhtT1IqAPbgHsmo4AUcuq0mqV30QtqmHWtly5OpCnh/2qW0ANOkD5scpVNsLJxgyY Oxud8483jaH1ptgn8jXVh3TKe1UDGci+4RWTD06MVlcu8hZWHUY+1prkaRIQab2BFB9I Y7fnp2ZiF+14E7tunsUiqnNWMAarkWHwWaYEBTeDRQdcsCfcOuRwhjeHAHkET3kW6FeZ TlszvvO4nMDUvdxGa+fNiR8qgyL8AvMlkX5DdBFeQwHIFhlfrhIzVWYO+I/BiieTNxzg KPp5jgWiWDmALgezycwIVldVIdjyK+ZZYcu2bCocQEbuKzTdRfAAgKK/zY9YjTro+Mds ldYg== X-Gm-Message-State: AOJu0YxX7v2tl0yaruY6vwC+uGX1PKliaOs4P+nQVC7o+dL4rD1+u66R 8yQ7mkQNB8+rz+iQWsmLkZoKki+TXaI0SuEfF7uaJ9W1A4ISPu5MSLgyfQhTDw== X-Gm-Gg: AeBDieuJPJ+MjkrklrcIl01/ed6rsry4qAMeZ41jsweAJ13luj/sQ40HGxv1E9+Fl1k eLHxXXSwn2bqm+Kg955QsoaBRnP3Ipy/zBYsD1nwZuM3GmUtGEZTsKGVEnZtZugYqE3S52V4t9S P5ng4PpbODGEYhe2T6uRlCP5NwOcgy95ux9oUuwdYWaMUN/qjbKT129q/0dHa3EaRle5CGNrVAU XNHjnrvCq3beSJOTukv8fPsluj0HodXqBhedflJ3bxTQoipy6B9oRpzp+MQr00PQ2VckGfSbJKM GtnXLr7PjS3dukJ9EE7BMSK5lFRLyjjdj4pWOscvRBg0VwSEvNILX0vkm08H8aamzRtQMqYbZJ5 O7VjmthUe6zOnJfvHr3uHrsTnuWGfyE8R2NFy1ytdtbkhDdl5n+n9M5DP8V/ptMvZRYVv7XvCLb mYmreSkFDEwLRwklvgnWZdk/q1+aUUUtOZe4/NZPt1La62gQ== X-Received: by 2002:a05:6000:40dd:b0:439:c1ca:82be with SMTP id ffacd0b85a97d-43d292e707fmr33150509f8f.28.1775665946544; Wed, 08 Apr 2026 09:32:26 -0700 (PDT) Received: from korli-neo50s.fritz.box ([2a02:1748:dd5c:c9e0:3b9a:93a3:a611:b878]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-43d1e2a6f08sm62223960f8f.6.2026.04.08.09.32.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 08 Apr 2026 09:32:26 -0700 (PDT) From: =?utf-8?b?SsOpcsO0bWUgRHV2YWw=?= To: gdb-patches@sourceware.org Cc: me@trungnt2910.com, =?utf-8?b?SsOpcsO0bWUgRHV2YWw=?= Subject: [PATCH v3 1/2] gdbserver: Haiku support Date: Wed, 8 Apr 2026 18:32:07 +0200 Message-ID: <20260408163208.6147-2-jerome.duval@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260408163208.6147-1-jerome.duval@gmail.com> References: <20260408163208.6147-1-jerome.duval@gmail.com> MIME-Version: 1.0 X-Spam-Status: No, score=-11.3 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org 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 . */ + +#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 . */ + +/* 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 +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#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 +[[nodiscard]] +std::enable_if_t, void>, + status_t> team_send (const team_debug_context *context, + haiku_nub_message_data &&data); + +template +[[nodiscard]] +std::enable_if_t, void>, + status_t> team_send (const team_debug_context *context, + haiku_nub_message_data &&data, + haiku_nub_message_reply &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 > 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 &)> callback) + { + if (m_thread < 0) + return B_NOT_INITIALIZED; + + std::shared_ptr gdbstatus; + + const auto make = [&] () -> target_waitstatus & { + gdbstatus = std::make_shared (); + 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 ( + 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 ( + 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 ( + 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 ( + 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 ( + 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 m_threads; + std::set m_created_threads; + std::queue > > + 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 ({ .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 + [[nodiscard]] + std::enable_if_t, void>, + status_t> + send (const haiku_nub_message_data &data) const + { + return haiku_send_nub_message (m_nub_port, data); + } + + template + [[nodiscard]] + std::enable_if_t, void>, + haiku_nub_message_reply > + send (haiku_nub_message_data &&data) const + { + data.reply_port = m_reply_port; + return haiku_send_nub_message (m_nub_port, data); + } + + template + [[nodiscard]] + std::enable_if_t, void>, + status_t> + send (haiku_nub_message_data &&data, + haiku_nub_message_reply &reply) const + { + data.reply_port = m_reply_port; + return haiku_send_nub_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 ( + { .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 &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 +[[nodiscard]] +std::enable_if_t, void>, + status_t> +team_send (const team_debug_context *context, + haiku_nub_message_data &&data) +{ + return context->send ( + std::forward > (data)); +} + +template +[[nodiscard]] +std::enable_if_t, void>, + status_t> +team_send (const team_debug_context *context, + haiku_nub_message_data &&data, + haiku_nub_message_reply &reply) +{ + return context->send ( + std::forward > (data), reply); +} + +static std::map > + team_debug_contexts; + +static std::mutex team_debug_ports_lock; +static std::set 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 &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 &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 context + = std::make_shared (); + + 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 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 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 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 chosen_context; + + if (ptid == minus_one_ptid) + { + /* Wait for any process. */ + bool block = !(target_options & TARGET_WNOHANG).raw (); + + std::vector > contexts; + std::vector 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 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 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 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 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 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 ( + { .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 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 (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 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 context; + RETURN_VALUE_AND_SET_ERRNO_IF_FAIL (get_context (ptid.pid (), context), + false); + + status_t status = context + ->send ( + { .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 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 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 + pipe_to_event_loop_closer (&pipe_to_event_loop, pipe_close); + + std::unique_ptr + 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 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 &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 &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 &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 &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 &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 &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 &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 &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 symbols (symbol_count); + /* An additional guaranteed null terminator. */ + std::vector 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 &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 &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 . */ + +#ifndef NAT_HAIKU_NAT_H +#define NAT_HAIKU_NAT_H + +#include + +#include + +#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 &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 &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 &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 &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 &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 &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 &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 &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 &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 &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 . */ + +#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 . */ + +#ifndef NAT_HAIKU_NUB_MESSAGE_H +#define NAT_HAIKU_NUB_MESSAGE_H + +#include "gnulib/config.h" + +#include + +#include + +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 class haiku_nub_message_traits +{ +}; + +#define HAIKU_ASSOCIATE_MESSAGE_DATA(message, data) \ + template <> class haiku_nub_message_traits \ + { \ + public: \ + typedef data data_type; \ + typedef void reply_type; \ + } + +#define HAIKU_ASSOCIATE_MESSAGE_DATA_WITH_REPLY(message, data) \ + template <> class haiku_nub_message_traits \ + { \ + 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 +using haiku_nub_message_data = + typename haiku_nub_message_traits::data_type; + +template +using haiku_nub_message_reply = + typename haiku_nub_message_traits::reply_type; + +template +std::enable_if_t, void>, + status_t> +haiku_send_nub_message (port_id nub_port, + const haiku_nub_message_data &data) +{ + return haiku_send_nub_message (nub_port, -1, message, &data, sizeof (data), + nullptr, 0); +} + +template +std::enable_if_t, void>, + haiku_nub_message_reply > +haiku_send_nub_message (port_id nub_port, + const haiku_nub_message_data &data) +{ + haiku_nub_message_reply 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 +std::enable_if_t, void>, + status_t> +haiku_send_nub_message (port_id nub_port, + const haiku_nub_message_data &data, + haiku_nub_message_reply &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 . */ + +#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 +int +all_teams (int (*for_each) (pid_t, const std::function &), + const std::function &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 &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 = "\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, + "" + "%s" + "%s" + "%s" + "", + osdata_table[i].type, + osdata_table[i].description, + osdata_table[i].title); + + buffer += "\n"; + + return buffer; + }, + }, + { + .type = "areas", + .title = "Areas", + .description = "Listing of all areas", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + all_teams (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, + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "", + 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 += "\n"; + return buffer; + }, + }, + { + .type = "comm", + .title = "Commpage symbols", + .description = "Listing of all symbols on the system commpage", + .take_snapshot = [] () -> std::string { + std::string buffer = "\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, + "" + "%s" + "%s" + "%s" + "%s" + "", + info.name, pulongest (info.value), + pulongest (info.size), type.c_str ()); + + return 0; + }); + + buffer += "\n"; + return buffer; + }, + }, + { + .type = "cpus", + .title = "CPUs", + .description = "Listing of all CPUs/cores on the system", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + for_each_cpu ([&] (const cpu_info &info) { + string_xml_appendf ( + buffer, + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "", + 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 += "\n"; + return buffer; + }, + }, + { + .type = "files", + .title = "File descriptors", + .description = "Listing of all file descriptors", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + all_teams (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, + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "", + plongest (info.team), plongest (info.number), mode.c_str (), + plongest (info.device), plongest (info.node), + (info.name != nullptr) ? info.name : "(unknown)"); + + return 0; + }); + + buffer += "\n"; + return buffer; + }, + }, + { + .type = "images", + .title = "Images", + .description = "Listing of all images", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + all_teams (for_each_image, [&] (const image_info &info) { + string_xml_appendf ( + buffer, + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "", + 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 += "\n"; + return buffer; + }, + }, + { + .type = "ports", + .title = "Ports", + .description = "Listing of all ports", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + all_teams (for_each_port, [&] (const port_info &info) { + string_xml_appendf (buffer, + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "", + plongest (info.team), plongest (info.id), + info.name, plongest (info.capacity), + plongest (info.queue_count), + plongest (info.total_count)); + + return 0; + }); + + buffer += "\n"; + return buffer; + }, + }, + { + .type = "sems", + .title = "Semaphores", + .description = "Listing of all semaphores", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + all_teams (for_each_sem, [&] (const sem_info &info) { + string_xml_appendf (buffer, + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "", + plongest (info.team), plongest (info.id), + info.name, plongest (info.count), + plongest (info.latest_holder)); + + return 0; + }); + + buffer += "\n"; + return buffer; + }, + }, + { + .type = "sockets", + .title = "Sockets", + .description = "Listing of all sockets", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + for_each_socket ([&] (const socket_info &info) { + string_xml_appendf (buffer, + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "", + 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 += "\n"; + return buffer; + }, + }, + { + .type = "teams", + .title = "Teams", + .description = "Listing of all teams", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + for_each_team ([&] (const team_info &info) { + string_xml_appendf (buffer, + "" + "%s" + "%s" + "%s" + "", + pulongest (info.pid), pulongest (info.uid), + info.args); + + return 0; + }); + + buffer += "\n"; + return buffer; + }, + }, + { + .type = "threads", + .title = "Threads", + .description = "Listing of all threads", + .take_snapshot = [] () -> std::string { + std::string buffer = "\n"; + + all_teams ( + for_each_thread, [&] (const thread_info &info) { + string_xml_appendf (buffer, + "" + "%s" + "%s" + "%s" + "", + plongest (info.team), plongest (info.tid), + info.name); + + return 0; + }); + + buffer += "\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 . */ + +#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 . */ + +#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 + +/* Very conservative inclusion of Haiku headers to prevent name clashes. */ +#include +#include + +/* 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 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, ®s) < 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, ®s) < 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, ®s) < 0) + perror_with_name (_("haiku_nat::set_cpu_state")); +} + +const gdb_byte * +haiku_amd64_target::sw_breakpoint_from_kind (int kind, int *size) +{ + /* From */ + + /* 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 . */ + +#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 . */ + +#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") From patchwork Wed Apr 8 16:32:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?b?SsOpcsO0bWUgRHV2YWw=?= X-Patchwork-Id: 132820 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 40BBC4BA2E13 for ; Wed, 8 Apr 2026 16:33:13 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 40BBC4BA2E13 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=jsRZ7nNi X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mail-wr1-x42b.google.com (mail-wr1-x42b.google.com [IPv6:2a00:1450:4864:20::42b]) by sourceware.org (Postfix) with ESMTPS id 693474BA2E0D for ; Wed, 8 Apr 2026 16:32:29 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 693474BA2E0D Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 693474BA2E0D Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::42b ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1775665949; cv=none; b=Tpxh5v0noFrf63bN1mSJPZXzRsrXrRoDC1JbbtkH4iskvFE2AlelzuzZUUaQ7p7b0qAv1wtph/xmJt6vbtIxD1+NewhsBMmccFOzMNyBong/UDcbfMa6jhEXi7Q1bVpqSMC+bMf+73qUTQ2uYO+NPnJCojWsfrx9byWYxCVRomI= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1775665949; c=relaxed/simple; bh=RcQBog65krVZcGMAtAuOhgT42hKKgqtQTnnkHhKW0os=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=WtvQssPDBY62tuUa/kU5OcwUZwnoUUGOxMcBbFsepQGsv2vVLQu3vriMcMsnOixAydVXgAN8lURfZZJwN9xxz2ScvCkhGMU1I7yHIxIErCHhZmm/DDqytz/KzbpvJmeOmKQUZaR4RKEmDKOzxNsNkyKUXqP2w6wPDgI+plsrOfQ= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 693474BA2E0D Received: by mail-wr1-x42b.google.com with SMTP id ffacd0b85a97d-43d0deb7ad5so202f8f.2 for ; Wed, 08 Apr 2026 09:32:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775665948; x=1776270748; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=N8g1rm96kQmM7njMt0IlapDiBGmS39+qI0Lo++0gRzc=; b=jsRZ7nNi1h0dqtu3FR8WSE1GBcB/6xqEUHS6r8SNFO3PnOJBE6pnvg7HDdwBkcmnud lg0mJO37uiFA+GfTxcNfw0PPqe9WnDX57UsWyCrH9aqiBpoHg/34y6//+S2OsSHF+r6l Facc8P//clF9D6oXoHWnzCJMI7WclnvkjMrZD5mnjuIFLR7KvI4nz4g9OL3yHejFvDpG QN9MrJkvCY9Msg5GtpnuCEEQNULVdEDm+a6bNhpyfMw/BGr/jufjuheqD5rqVVmzYeEa VrNenw3sUZJPtTfGgz4ai4KxWe0J9WgUGNeJXYNFmpJYA6Ij2JCpLUnKAKtwXdAiMarD DEyQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775665948; x=1776270748; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=N8g1rm96kQmM7njMt0IlapDiBGmS39+qI0Lo++0gRzc=; b=bnKytFTukVwk5kDWZfs/SShSIY+nh7Sqn/i3qiciZ2L/YCWYxA+g34sIYwZaHUVNi3 n5rRi+fKRfO9vg23VTXh8XgcHgS03jcVgTy0d1EELxIy4z9BCZjevs5+Tj5KaaDdc+zL NiB/ir6jLh18E4+t03U8Jiv94Tj6GsSyq688S2UIrvOkj1oBiA4Uv9yA9C8bqM3/eK8o Oo0paXpxHoE2SY7XWYHpZIMkA3YfliRSddQRwJrZp1uNcg7FBhII+oIRI0ZUhljllR2o +HVXaNhJoz/FTe2f0za2CcHDKmnU2Fz0134HG/hdNEK3yfVRaKQ3eGSNB8r55zvOxQ60 54NQ== X-Gm-Message-State: AOJu0YzTXMCB4ns/oSr7h2XnANaVlFem5k0fSHC1a6CUF3UoJOSbwG8v 82zXWWDAHtscaF7wE1L3l7L0SSiD7R4zbzNkizoITSPC45DnAha7C+LnHPwn3A== X-Gm-Gg: AeBDiesXXZMwcQO5nhEUSWqKro45hBR8F0CbtEiejDmfhe7A1oZhzS8zrNsqAgVNXeC NORlnakaGb1h4gwT+slm2TTjSyLCMbgo8psNlMSx6hb9/IUgvFa9MO+7BQxw/cWlUN73GD/ajFh 2V8MQV8rquMWvRA7N83iuABRUu4Z+mT76mVaVs85DUuhOwC2YJm799aLsM53YvqVpAYgkyWYXPP orWrrlp+0hncM9SmKi8NEzCOt4T1lJkGpV3hyF4TYgvxwDC5zMoF1oIF4KXgTAxh/wzVCtFv99i R/9GhT45sgmqIFzRx2nikbglZgw6/ieHRwwAmzvJjo8bxWAGckumZD/m+5aVhqBYeM/YQCOWH2o ZOssU64af6pQbj2rR3goXPlguQ1q/6urHIzq4L24Z2K+ulO3AoeyuWj65dZ/DTGyIMfUJ1gHjJn VBBQxcPKH6ZKdfFi1BFW4bsoSVipO//+KYrQigboFepLG3DQ== X-Received: by 2002:a05:6000:144a:b0:439:b55d:b0e5 with SMTP id ffacd0b85a97d-43d292cd508mr33394238f8f.28.1775665947325; Wed, 08 Apr 2026 09:32:27 -0700 (PDT) Received: from korli-neo50s.fritz.box ([2a02:1748:dd5c:c9e0:3b9a:93a3:a611:b878]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-43d1e2a6f08sm62223960f8f.6.2026.04.08.09.32.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 08 Apr 2026 09:32:26 -0700 (PDT) From: =?utf-8?b?SsOpcsO0bWUgRHV2YWw=?= To: gdb-patches@sourceware.org Cc: me@trungnt2910.com, =?utf-8?b?SsOpcsO0bWUgRHV2YWw=?= Subject: [PATCH v3 2/2] gdb: Haiku support Date: Wed, 8 Apr 2026 18:32:08 +0200 Message-ID: <20260408163208.6147-3-jerome.duval@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260408163208.6147-1-jerome.duval@gmail.com> References: <20260408163208.6147-1-jerome.duval@gmail.com> MIME-Version: 1.0 X-Spam-Status: No, score=-10.9 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org 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. Reviewed-By: Eli Zaretskii --- 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 . */ + +#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 + +/* 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 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 . */ + +#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 (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 . */ + +#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 = "\n"; + haiku_nat::for_each_image ( + current_inferior ()->pid, [&] (const haiku_nat::image_info &info) { + if (!info.is_main_executable) + { + document += string_printf ( + " " + "\n", + info.name, + paddress (current_inferior ()->arch (), info.text)); + } + return 0; + }); + document += "\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 +haiku_nat_target::memory_map () +{ + std::vector 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 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 . */ + +#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 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 . */ + +#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 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_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 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 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 . */ + +#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 . */ -#ifndef NAT_HAIKU_NAT_H -#define NAT_HAIKU_NAT_H +#ifndef GDB_NAT_HAIKU_NAT_H +#define GDB_NAT_HAIKU_NAT_H #include @@ -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 . */ -#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 . */ -#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 . */ + +#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 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 (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 +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 . */ + +#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)