From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id abADNJf3smkDPCYAWB0awg (envelope-from ) for ; Thu, 12 Mar 2026 13:27:51 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=CpWEd732; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id CF600378002; Thu, 12 Mar 2026 13:27:51 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_MSPIKE_H2,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id E00DF1E089 for ; Thu, 12 Mar 2026 13:27:45 -0400 (EDT) Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 6283D4BAD14F for ; Thu, 12 Mar 2026 17:27:45 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6283D4BAD14F Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=CpWEd732 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 9F5C94BBCDDB for ; Thu, 12 Mar 2026 17:25:43 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 9F5C94BBCDDB 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 9F5C94BBCDDB 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=1773336349; cv=none; b=yB9Fp/o3XGVjVK3+5CpeHTcAjpIegvea2tqO0IOaXsFE82k1mQr8MhYPTFBWQscTqp+z6CnnKspZFrSI7HH/HpJwUszfC0r7RAAwuTiJUNV5x0dK3nW7GIODbpKf22OTxKPAR2OhkjFZ1X3Etf8YUSglXaMRYQ8kPpceRuj0a2c= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1773336349; c=relaxed/simple; bh=l6yO0EJvLgmBbRcEzYYSyst1REgYBN3ahI2sNLAMEgY=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=lsgtQsK8Mm4po1GISBGVAqtc3CVti/iU6Gmcq1UMGrFOklzHPA2qMSmQqsU5rt9ohdymi1ZtSRTURfxKcXupgx8+XVIfHiNQp0u+3RD+pUnA2SwbwIPsvpiNsGbA6839PzXEl25YDdqzxaF14721uByi1Bd/yxkZ/FMyylvrvC8= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9F5C94BBCDDB Received: by mail-wr1-x42a.google.com with SMTP id ffacd0b85a97d-439a89b6fd0so1268241f8f.2 for ; Thu, 12 Mar 2026 10:25:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773336342; x=1773941142; 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=OCxWkXib/H2Y9i+Q1ijDaR2Tjh+dtcvJvq454ooZdWE=; b=CpWEd732snQvQzCyGea8c/DdIwGhWd307cT26l8tRsLMJzhjZcGUpiBxm05kmYDEJq m61+vMIa2dFT3Viyk7vm+sdz6nNFFUqEQr567hV5ftNirBGZ/ZIeGpX1o9OYdwTW5XXC HbQsDI4PIo+Xu9zhYTmgyaM9zAbZA57rikCRN9PFn1lHkHh1ABCbItpz8BZFvyipbjZ3 a83y0xNm6Pam4BTW76CZkva053BFndf9nhWU3ImtqEapzj16uPLleFYTsXS1k25aORXI mIuTkHgCuK5vUtwQcZkFJ1jTcM6xf0lvJDjo2aY1yGo6Lz+evxuPP21i3PhVI7xJm6TA /IqA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773336342; x=1773941142; 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=OCxWkXib/H2Y9i+Q1ijDaR2Tjh+dtcvJvq454ooZdWE=; b=reshpccqjBVBYWgaEBJCsZ51HQjep5ii9QLhmJ6veDmkpsD9mBqs0pvnXxzLsJ+Wzz hnbjZRwSlkzIZiOA8bKleUB8fR3JsRUB3IVlwDX/5lyHTwRlO1biqDPJD8DCJ0YIdi2A Yr4u9fiI4Yqikku519SegWgGMTUv1fWe9Qs7N84W0prAH+wMjKwBbf5OIpPLw2LRQgZe 9QQcjnFuv9nlKksppS2/+1SoMNq17+vi9Kuvio9z7Qy0JICzeIQ2GorjsWkgX+FkykIw C9hMvCNpKn2+ARg/hUPKZslfD3tHpUKGAje9ofpyl/n47T+ZTY2PXUAW8+nNXMrV3qSD W2uw== X-Gm-Message-State: AOJu0YwXZpOlag5qVX6oftMfWjodwQaHkwHmvJaAgphTmUmHzcCZSIzV iNHtcoVeIUz0S5I7C4iu1R/EjHszsWQ/m7OJ8fXoz3x6GpA5VURoBKLmsxLxmQ== X-Gm-Gg: ATEYQzxc8VybmeO/6foUeBMIetNEzBnVkSX9Ef/f6gJoBSB3OaP68QxgHjaLku8JbgC EpqJrWXfoDegZ+aUtAtee3EBBMMHoPuH57Me+R1yFI+jzBJbAIX99MMBkganCllF4ojZdc8m398 G1Ob9rxC7SxNS4DhVG5Y9pdwahfv2X78zy48ejgNh+QNQEfkGMuqQtRVfUuhV6Ai7irSC55hSwR 5wWlDYVgn9eT5oZhHwtnfSSRhNsOpdCdVDaVCRBEyri1bjlOEP6OZWweBuGKNYIVHRNZHSVVJqy CjwyEgE/rP+LRYQJPX+7SAudbif0LbC9KZh+1v9s7rvXPwx3UK8uQIBjx3chKANYKJZRAwlPCFd uBeIlLCD9RR1aXok0vQB/JEycmxDZD4krYDwHmsmdaqqr9vuUeNP2Gl79vXnjNGBzMji+bKG/bm Zg1yYVu7WSYoohuESOiPusnZTLikWRyUWVOh2Vy9n4zU6VdQ== X-Received: by 2002:a05:6000:1aca:b0:439:ccd7:cdfb with SMTP id ffacd0b85a97d-43a04dd4fecmr891576f8f.53.1773336340812; Thu, 12 Mar 2026 10:25:40 -0700 (PDT) Received: from korli-neo50s.fritz.box ([2a02:1748:dd5c:c9e0:d9f5:e2d7:f7e3:a577]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-439fe20b544sm10357893f8f.20.2026.03.12.10.25.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 12 Mar 2026 10:25:40 -0700 (PDT) From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= To: gdb-patches@sourceware.org Cc: Trung Nguyen <57174311+trungnt2910@users.noreply.github.com> Subject: [PATCH 1/8] gdbserver: Initial Haiku support Date: Thu, 12 Mar 2026 18:23:29 +0100 Message-ID: <20260312172336.15450-2-jerome.duval@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260312172336.15450-1-jerome.duval@gmail.com> References: <20260312172336.15450-1-jerome.duval@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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~public-inbox=simark.ca@sourceware.org From: Trung Nguyen <57174311+trungnt2910@users.noreply.github.com> --- 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 | 609 ++++++++ gdbserver/haiku-low.h | 100 ++ gdbserver/remote-utils.cc | 4 + gdbsupport/signals.cc | 10 + include/gdb/signals.def | 4 +- 16 files changed, 4946 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..d1796a40 --- /dev/null +++ b/gdb/nat/haiku-debug.c @@ -0,0 +1,43 @@ +/* Haiku re-exports for debugging functions with conflicting names. + + Copyright (C) 2024 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..30f00926 --- /dev/null +++ b/gdb/nat/haiku-nat.c @@ -0,0 +1,2808 @@ +/* Internal interfaces for the Haiku code. + + Copyright (C) 2024 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, struct 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..4a057a45 --- /dev/null +++ b/gdb/nat/haiku-nat.h @@ -0,0 +1,429 @@ +/* Internal interfaces for the Haiku code. + + Copyright (C) 2024 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..815377d5 --- /dev/null +++ b/gdb/nat/haiku-nub-message.c @@ -0,0 +1,50 @@ +/* Haiku nub messages support. + + Copyright (C) 2024 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..04212a3a --- /dev/null +++ b/gdb/nat/haiku-nub-message.h @@ -0,0 +1,141 @@ +/* Haiku nub messages support. + + Copyright (C) 2024 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..1f295c45 --- /dev/null +++ b/gdb/nat/haiku-osdata.c @@ -0,0 +1,445 @@ +/* Haiku-specific functions to retrieve OS data. + + Copyright (C) 2024 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 (struct 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..01e87e4d --- /dev/null +++ b/gdb/nat/haiku-osdata.h @@ -0,0 +1,26 @@ +/* Haiku-specific functions to retrieve OS data. + + Copyright (C) 2024 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..0e6e0ade --- /dev/null +++ b/gdbserver/haiku-amd64-low.cc @@ -0,0 +1,262 @@ +/* Copyright (C) 2024 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 (struct regcache *regcache, int regno) +{ + char regs[sizeof (x86_64_debug_cpu_state)]; + + if (haiku_nat::get_cpu_state (ptid_of (current_thread), ®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 (struct regcache *regcache, int regno) +{ + char regs[sizeof (x86_64_debug_cpu_state)]; + + if (haiku_nat::get_cpu_state (ptid_of (current_thread), ®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 (ptid_of (current_thread), ®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 *tdesc = amd64_create_target_description (X86_XSTATE_AVX_MASK, + false, false, false); + + init_target_desc (tdesc, amd64_expedite_regs); + + process->tdesc = tdesc; +} + +/* 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..41295d58 --- /dev/null +++ b/gdbserver/haiku-low.cc @@ -0,0 +1,609 @@ +/* Copyright (C) 2024 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::vector &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::continue_process")); + }; + + std::string str_program_args = construct_inferior_arguments (program_args); + + 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, str_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 (struct 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, struct 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. */ + 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) + 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: + remove_thread (find_thread_ptid (wptid)); + + 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 (pid_of (process)) < 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 (pid_of (process)) < 0) + return -1; + + mourn (process); + return 0; +} + +/* Implement the mourn target_ops method. */ + +void +haiku_process_target::mourn (struct process_info *proc) +{ + for_each_thread (pid_of (proc), remove_thread); + + remove_process (proc); +} + +/* Implement the join target_ops method. */ + +void +haiku_process_target::join (int pid) +{ + gdb::handle_eintr (-1, ::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 (pid_of (current_process ()), 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 (pid_of (current_process ()), 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 (pid_of (thread), 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 (pid_of (current_process ()), 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 (ptid_of (thread)); +} + +/* 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..11673055 --- /dev/null +++ b/gdbserver/haiku-low.h @@ -0,0 +1,100 @@ +/* Copyright (C) 2024 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::vector &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 7671206e..aec14dbc 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 eb321805..91beaab9 100644 --- a/gdbsupport/signals.cc +++ b/gdbsupport/signals.cc @@ -333,6 +333,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) { @@ -590,6 +595,11 @@ do_gdb_signal_to_host (enum gdb_signal oursig, return SIGLIBRT; #endif +#if defined (SIGKILLTHR) + case GDB_SIGNAL_SIGKILLTHR: + return SIGKILLTHR; +#endif + default: #if defined (REALTIME_LO) retsig = 0; diff --git a/include/gdb/signals.def b/include/gdb/signals.def index c6819b0f..0d4bb011 100644 --- a/include/gdb/signals.def +++ b/include/gdb/signals.def @@ -196,7 +196,9 @@ SET (GDB_EXC_BREAKPOINT, 150, "EXC_BREAKPOINT", "Breakpoint") SET (GDB_SIGNAL_LIBRT, 151, "SIGLIBRT", "librt internal signal") +SET (GDB_SIGNAL_SIGKILLTHR, 152, "SIGKILLTHR", "Thread killed") + /* If you are adding a new signal, add it just above this comment. */ /* Last and unused enum value, for sizing arrays, etc. */ -SET (GDB_SIGNAL_LAST, 152, NULL, "GDB_SIGNAL_LAST") +SET (GDB_SIGNAL_LAST, 153, NULL, "GDB_SIGNAL_LAST") -- 2.43.0