From patchwork Wed Nov 30 18:26:05 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yuly Tarasov X-Patchwork-Id: 61274 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id A3785385B1BC for ; Wed, 30 Nov 2022 18:27:05 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from forward106o.mail.yandex.net (forward106o.mail.yandex.net [IPv6:2a02:6b8:0:1a2d::609]) by sourceware.org (Postfix) with ESMTPS id 968C53858D37 for ; Wed, 30 Nov 2022 18:26:45 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 968C53858D37 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=syntacore.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=syntacore.com Received: from iva5-b9ce8295c822.qloud-c.yandex.net (iva5-b9ce8295c822.qloud-c.yandex.net [IPv6:2a02:6b8:c0c:7f93:0:640:b9ce:8295]) by forward106o.mail.yandex.net (Yandex) with ESMTP id CB1F4568F211 for ; Wed, 30 Nov 2022 21:26:43 +0300 (MSK) Received: by iva5-b9ce8295c822.qloud-c.yandex.net (smtp/Yandex) with ESMTPSA id gQW6c54ZNiE1-44IjUi03; Wed, 30 Nov 2022 21:26:43 +0300 X-Yandex-Fwd: 1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=syntacore.com; s=mail; t=1669832803; bh=mAMLdOHnjbE5xoBUGZ8rV8gcoQBqbKk6xjYNdIeho6M=; h=Message-Id:Date:Cc:Subject:To:From; b=FHvS5WjtLvU0HgBqCuUW3Hz8YAwOyzf3Wb6MUPtSB1VUpPo+jk74YQ6mUPdO/UJ3y kpcsaOiVJWGifs9IplFzvzTJi/6PBvjm6VbxE38HafK6G87VQqs1rDKqU1PM7wT+dl GOrdzjSDQ2R9R/LqbWGI6dVOezg+2Gey3Ag8h1F4= Authentication-Results: iva5-b9ce8295c822.qloud-c.yandex.net; dkim=pass header.i=@syntacore.com From: Yuly Tarasov To: gdb-patches@sourceware.org Cc: Yuly Tarasov Subject: [PATCH] [RFC] Add basic support for HW breakpoints/watchpoints with RISC-V target Date: Wed, 30 Nov 2022 21:26:05 +0300 Message-Id: <20221130182605.1905317-1-yuly.tarasov@syntacore.com> X-Mailer: git-send-email 2.38.1 MIME-Version: 1.0 X-Spam-Status: No, score=-13.0 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_INFOUSMEBIZ, KAM_SHORT, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces+patchwork=sourceware.org@sourceware.org Sender: "Gdb-patches" Currently Debug Spec 1.0 for RISC-V is under active discussion. Important new feature of this document is new Sdtrig ISA Extension. This extension introduces trigger registers. Those registers can be used to effectively support hardware breakpoints and watchpoints. By now there were no hardware breakpoints support for RISC-V in the GDB at all. This patch introduces support for hardware breakpoints and watchpoints. Basic idea is: for each process there is a structure that contains info about debug registers state. This state contains an array of structures that describe active hardware breakpoints/watchpoints. When new breakpoint/watchpoint added no immediate ptrace call happen. Instead internal debug registers state is updated and ptrace call postponed to thread resume. This allows to reduce communication with kernel API. Basically, it is the same idea that already implemented in aarch64, mips, x86 and so on. I just did it for RISC-V. Will appreciate any comments and suggestions. I need to note that Debug Spec 1.0 is still under discussion, so this patch is to collect community feedback, not for commit. For this patch you will need Linux kernel support. Patch is here: https://marc.info/?l=linux-arch&m=166725156213127&w=2 --- Best regards, Yuly Tarasov --- gdb/Makefile.in | 1 + gdb/configure.nat | 2 + gdb/nat/riscv-linux-hw-point.c | 519 +++++++++++++++++++++++++++++++++ gdb/nat/riscv-linux-hw-point.h | 176 +++++++++++ gdb/nat/riscv-linux.c | 78 +++++ gdb/nat/riscv-linux.h | 30 ++ gdb/riscv-linux-nat.c | 331 ++++++++++++++++++++- gdbserver/configure.srv | 2 + gdbserver/linux-riscv-low.cc | 206 +++++++++++++ 9 files changed, 1342 insertions(+), 3 deletions(-) create mode 100644 gdb/nat/riscv-linux-hw-point.c create mode 100644 gdb/nat/riscv-linux-hw-point.h create mode 100644 gdb/nat/riscv-linux.c create mode 100644 gdb/nat/riscv-linux.h diff --git a/gdb/Makefile.in b/gdb/Makefile.in index aecab41eeb..553352fb7f 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -1543,6 +1543,7 @@ HFILES_NO_SRCDIR = \ nat/linux-waitpid.h \ nat/mips-linux-watch.h \ nat/ppc-linux.h \ + nat/riscv-linux-hw-point.h \ nat/x86-cpuid.h \ nat/x86-dregs.h \ nat/x86-gcc-cpuid.h \ diff --git a/gdb/configure.nat b/gdb/configure.nat index b45519fd11..441cd4d709 100644 --- a/gdb/configure.nat +++ b/gdb/configure.nat @@ -290,6 +290,8 @@ case ${gdb_host} in riscv*) # Host: RISC-V, running Linux NATDEPFILES="${NATDEPFILES} riscv-linux-nat.o \ + nat/riscv-linux.o \ + nat/riscv-linux-hw-point.o \ nat/riscv-linux-tdesc.o" ;; s390) diff --git a/gdb/nat/riscv-linux-hw-point.c b/gdb/nat/riscv-linux-hw-point.c new file mode 100644 index 0000000000..1f0ed82981 --- /dev/null +++ b/gdb/nat/riscv-linux-hw-point.c @@ -0,0 +1,519 @@ +/* Copyright (C) 2022 Free Software Foundation, Inc. + Contributed by Syntacore + + 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 "riscv-linux-hw-point.h" + +#include "elf/common.h" /* NT_ARM_HW_BREAK, NT_ARM_HW_POINT */ +#include "nat/linux-nat.h" + +#include + +/* The order in which and are included + can be important. often declares various PTRACE_* + enums. often defines preprocessor constants for + these very same symbols. When that's the case, build errors will + result when is included before . */ +#include +#include + +#include + +bool +arch_lwp_info::has_n_changed (unsigned n) const noexcept +{ + return dr_changed_hwbp & (1u << n); +} + +bool +arch_lwp_info::has_changed () const noexcept +{ + return dr_changed_hwbp != 0; +} + +void +arch_lwp_info::mark_n_changed (unsigned n) noexcept +{ + dr_changed_hwbp |= 1u << n; +} + +void +arch_lwp_info::mark_all_changed (unsigned m) noexcept +{ + gdb_assert (sizeof (dr_changed_hwbp) * 8 >= m); + dr_changed_hwbp = (1u << m) - 1; +} + +void +arch_lwp_info::clear_changed () noexcept +{ + dr_changed_hwbp = 0; +} + +riscv_hwbp::riscv_hwbp (CORE_ADDR addr, target_hw_bp_type type, + unsigned l) noexcept : p_addr (addr), + p_type (type), + p_len (l), + ref_count (0) +{ +} + +void +riscv_hwbp::set_point (CORE_ADDR addr, target_hw_bp_type type, + unsigned l) noexcept +{ + p_addr = addr; + p_type = type; + p_len = l; +} + +target_hw_bp_type +riscv_hwbp::type () const noexcept +{ + return p_type; +} + +unsigned +riscv_hwbp::type_for_dbg_reg () const +{ + switch (p_type) + { + case hw_execute: + return HWBP; + case hw_read: + return RWP; + case hw_write: + return WWP; + case hw_access: + return RWWP; + } + + gdb_assert_not_reached ("unexpected hwbp type"); +} + +CORE_ADDR +riscv_hwbp::addr () const noexcept { return p_addr; } + +unsigned +riscv_hwbp::len () const noexcept +{ + return p_len; +} + +bool +riscv_hwbp::is_wp () const noexcept +{ + return p_type != hw_execute; +} + +bool +riscv_hwbp::is_wp_at_addr (CORE_ADDR addr) const noexcept +{ + return is_wp () && (p_addr == addr); +} + +bool +riscv_hwbp::is_busy () const noexcept +{ + return ref_count != 0; +} + +bool +riscv_hwbp::is_aligned () const noexcept +{ + unsigned int alignment = is_wp () + ? riscv_debug_reg_state::riscv_hwwp_alignment + : riscv_debug_reg_state::riscv_hwbp_alignment; + + if (p_addr & (alignment - 1)) + return false; + + return true; +} + +void +riscv_hwbp::clear () noexcept +{ + *this = riscv_hwbp{}; +} + +bool +riscv_hwbp::operator== (const riscv_hwbp &other) const noexcept +{ + return p_type == other.p_type && p_addr == other.p_addr + && p_len == other.p_len; +} + +unsigned +riscv_hwbp::n_refs () const noexcept +{ + return ref_count; +} + +void +riscv_hwbp::inc_ref_count () noexcept +{ + ++ref_count; +} + +void +riscv_hwbp::dec_ref_count () noexcept +{ + --ref_count; +} + +unsigned +riscv_debug_reg_state::num_hwbp_regs () const noexcept +{ + return riscv_num_hwbp_regs; +} + +bool +riscv_debug_reg_state::has_wp_at_addr (CORE_ADDR addr) const +{ + return std::any_of ( + dr_bp_states.begin (), dr_bp_states.end (), + [addr] (const auto &bp) { return bp.is_wp_at_addr (addr); }); +} + +CORE_ADDR +riscv_debug_reg_state::wp_orig_addr (CORE_ADDR addr) const +{ + auto hwwp = std::find_if ( + dr_bp_states.begin (), dr_bp_states.end (), + [addr] (const auto &bp) { return bp.is_wp_at_addr (addr); }); + return hwwp != dr_bp_states.end () ? hwwp->addr () + : static_cast (0); +} + +bool +riscv_debug_reg_state::insert_one_point (const riscv_hwbp &point) +{ + gdb_assert (point.is_aligned ()); + + auto inserting_bp + = std::find (dr_bp_states.begin (), dr_bp_states.end (), point); + if (inserting_bp == dr_bp_states.end ()) + { + inserting_bp = std::find_if ( + dr_bp_states.begin (), dr_bp_states.end (), + [] (const auto &bp) { return bp.is_busy () == false; }); + + if (inserting_bp == dr_bp_states.end ()) + { + return false; + } + else + { + gdb_assert (inserting_bp->n_refs () == 0); + } + } + else + { + gdb_assert (inserting_bp->n_refs () != 0); + } + + if (inserting_bp->is_busy () == false) + { + *inserting_bp = point; + notify_debug_reg_change (inserting_bp - dr_bp_states.begin ()); + } + + inserting_bp->inc_ref_count (); + + return true; +} + +bool +riscv_debug_reg_state::remove_one_point (const riscv_hwbp &point) +{ + gdb_assert (point.is_aligned ()); + + auto inserting_bp + = std::find (dr_bp_states.begin (), dr_bp_states.end (), point); + if (inserting_bp == dr_bp_states.end ()) + { + return false; + } + else + { + gdb_assert (inserting_bp->n_refs () != 0); + } + + inserting_bp->dec_ref_count (); + + if (inserting_bp->n_refs () == 0) + { + inserting_bp->clear (); + notify_debug_reg_change (inserting_bp - dr_bp_states.begin ()); + } + + return true; +} + +bool +riscv_debug_reg_state::handle_point (const riscv_hwbp &point, + point_action action) +{ + if (action == point_action::INSERT) + { + if (!point.is_aligned ()) + return false; + + return insert_one_point (point); + } + else + return remove_one_point (point); +} + +/* Call ptrace to set the thread TID's hardware breakpoint/watchpoint + registers with data from *STATE. */ + +void +riscv_debug_reg_state::linux_set_debug_regs (int tid) +{ + struct iovec iov; + struct user_hwdebug_state regs; + + memset (®s, 0, sizeof (regs)); + iov.iov_base = ®s; + + int count = num_hwbp_regs (); + if (count == 0) + return; + + iov.iov_len = (offsetof (struct user_hwdebug_state, dbg_regs) + + count * sizeof (regs.dbg_regs[0])); + + for (int i = 0; i < count; i++) + { + regs.dbg_regs[i].addr = dr_bp_states[i].addr (); + regs.dbg_regs[i].type = dr_bp_states[i].type_for_dbg_reg (); + regs.dbg_regs[i].len = dr_bp_states[i].len (); + } + + if (ptrace (PTRACE_SETREGSET, tid, NT_ARM_HW_WATCH, (void *)&iov)) + { + error (_ ("Unexpected error setting hardware debug registers")); + } +} + +bool +riscv_debug_reg_state::linux_any_set_debug_regs () +{ + int count = num_hwbp_regs (); + if (count == 0) + return false; + + return dr_bp_states.end () + != std::find_if (dr_bp_states.begin (), dr_bp_states.end (), + [] (const auto &hwbp) { return hwbp.is_busy (); }); +} + +/* Helper for riscv_debug_reg_state::notify_debug_reg_change. Records the + information about the change of one hardware breakpoint/watchpoint + setting for the thread LWP. The actual updating of hardware debug registers + is not carried out until the moment the thread is resumed. */ + +static int +debug_reg_change_callback (struct lwp_info *lwp, unsigned int idx) +{ + int tid = ptid_of_lwp (lwp).lwp (); + arch_lwp_info *info = lwp_arch_private_info (lwp); + + if (info == nullptr) + { + info = XCNEW (struct arch_lwp_info); + lwp_set_arch_private_info (lwp, info); + } + + if (show_debug_regs) + { + debug_printf ("debug_reg_change_callback: \n\tOn entry:\n"); + debug_printf ("\ttid%d, dr_changed_hwbp=0x%08u\n", tid, + info->dr_changed_hwbp); + } + + gdb_assert (idx >= 0 && (idx < riscv_debug_reg_state::riscv_hwbp_max_num)); + + /* The actual update is done later just before resuming the lwp, + we just mark that one register pair needs updating. */ + info->mark_n_changed (idx); + + /* If the lwp isn't stopped, force it to momentarily pause, so + we can update its debug registers. */ + if (!lwp_is_stopped (lwp)) + linux_stop_lwp (lwp); + + if (show_debug_regs) + { + debug_printf ("\tOn exit:\n\ttid%d, dr_changed_hwbp=0x%08u\n", tid, + info->dr_changed_hwbp); + } + + return 0; +} + +void +riscv_debug_reg_state::notify_debug_reg_change (unsigned int idx) const +{ + ptid_t pid_ptid = ptid_t (current_lwp_ptid ().pid ()); + + iterate_over_lwps (pid_ptid, [=] (struct lwp_info *info) { + return debug_reg_change_callback (info, idx); + }); +} + +void +riscv_debug_reg_state::show_debug_regs (const char *func, + const riscv_hwbp &point) const +{ + debug_printf ("%s", func); + if (point.is_busy ()) + { + const char *hwbp_type = "??unknown??"; + switch (point.type ()) + { + case hw_write: + hwbp_type = "hw-write-watchpoint"; + break; + case hw_read: + hwbp_type = "hw-read-watchpoint"; + break; + case hw_access: + hwbp_type = "hw-access-watchpoint"; + break; + case hw_execute: + hwbp_type = "hw-breakpoint"; + break; + } + + debug_printf (" (addr=0x%08" PRIx64 ", len=%u, type=%s)", point.addr (), + point.len (), hwbp_type); + } + + debug_printf (":\n"); + debug_printf ("\tHWPOINTs:\n"); + + int i = 0; + std::for_each (dr_bp_states.begin (), dr_bp_states.end (), + [&i] (const auto &bp) { + debug_printf ("\t%cP%d: ", bp.is_wp () ? 'W' : 'H', i++); + debug_printf ("addr=0x%08" PRIx64 ", ", bp.addr ()); + debug_printf ("len=%u, ", bp.len ()); + debug_printf ("ref.count=%d\n", bp.n_refs ()); + }); +} + +void +riscv_debug_reg_state::linux_get_debug_reg_capacity (int pid) +{ + struct iovec iov; + struct user_hwdebug_state dbg_state; + + iov.iov_base = &dbg_state; + iov.iov_len = sizeof (dbg_state); + + /* Get hardware watchpoint register info. */ + if (ptrace (PTRACE_GETREGSET, pid, NT_ARM_HW_WATCH, &iov) == 0 + && dbg_state.dbg_info > 0) + { + riscv_num_hwbp_regs = dbg_state.dbg_info; + if (riscv_num_hwbp_regs > riscv_hwbp_max_num) + { + warning (_ ("Unexpected number of hardware watchpoint registers" + " reported by ptrace, got %d, expected %d."), + riscv_num_hwbp_regs, riscv_hwbp_max_num); + riscv_num_hwbp_regs = riscv_hwbp_max_num; + } + } + else + { + warning (_ ("Unable to determine the number of hardware watchpoints" + " available.")); + riscv_num_hwbp_regs = 0; + } +} + +static riscv_process_info *riscv_process_info_list = nullptr; + +riscv_process_info::riscv_process_info ( + pid_t p, riscv_process_info *to_be_next) noexcept : next (to_be_next), + pid (p), + state () +{ +} + +riscv_debug_reg_state & +riscv_process_info::get_state () noexcept +{ + return state; +} + +riscv_process_info & +riscv_process_info::add_process_info (int pid) +{ + riscv_process_info *new_proc_info + = new riscv_process_info (pid, riscv_process_info_list); + riscv_process_info_list = new_proc_info; + return *new_proc_info; +} + +riscv_process_info & +riscv_process_info::get_process_info (int pid) +{ + for (auto *it = riscv_process_info_list; it != nullptr; it = it->next) + if (it->pid == pid) + return *it; + + return add_process_info (pid); +} + +void +riscv_process_info::forget_process (int pid) noexcept +{ + riscv_process_info *prev = riscv_process_info_list; + for (auto *it = riscv_process_info_list; it != nullptr; + prev = it, it = prev->next) + { + if (it->pid == pid) + { + if ((it == riscv_process_info_list) && (it->next == nullptr)) + { + delete it; + riscv_process_info_list = nullptr; + return; + } + + prev->next = it->next; + delete it; + return; + } + } +} + +riscv_debug_reg_state & +riscv_process_info::get_state (int pid) +{ + return get_process_info (pid).get_state (); +} + +void +riscv_process_info::linux_get_debug_reg_capacity (int pid) +{ + get_state (pid).linux_get_debug_reg_capacity (pid); +} diff --git a/gdb/nat/riscv-linux-hw-point.h b/gdb/nat/riscv-linux-hw-point.h new file mode 100644 index 0000000000..f8ee656431 --- /dev/null +++ b/gdb/nat/riscv-linux-hw-point.h @@ -0,0 +1,176 @@ +/* Copyright (C) 2022 Free Software Foundation, Inc. + Contributed by Syntacore + + 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_RISCV_LINUX_HW_POINT_H +#define NAT_RISCV_LINUX_HW_POINT_H + +#include "gdbsupport/break-common.h" /* For enum target_hw_bp_type. */ + +#include +#include + +/* Each bit of a variable of this type is used to indicate whether a + hardware breakpoint or watchpoint setting has been changed since + the last update. + + Bit N corresponds to the Nth hardware breakpoint or watchpoint + setting which is managed in aarch64_debug_reg_state, where N is + valid between 0 and the total number of the hardware breakpoint or + watchpoint debug registers minus 1. + + When bit N is 1, the corresponding breakpoint or watchpoint setting + has changed, and therefore the corresponding hardware debug + register needs to be updated via the ptrace interface. + + This type is part of the mechanism which helps reduce the number of + ptrace calls to the kernel, i.e. avoid asking the kernel to write + to the debug registers with unchanged values. */ + +struct arch_lwp_info final +{ + unsigned dr_changed_hwbp; + + [[nodiscard]] bool has_n_changed (unsigned n) const noexcept; + [[nodiscard]] bool has_changed () const noexcept; + void mark_n_changed (unsigned n) noexcept; + void mark_all_changed (unsigned m) noexcept; + void clear_changed () noexcept; +}; + +enum riscv_hwbp_type +{ + HWBP = 0, + RWP = 1, + WWP = 2, + RWWP = 3, +}; + +/* Describes hardware breakpoint/watchpoint. It stores point address, point + type, point len and number of references to the point. This class is used to + describe debug regs (see riscv_debug_reg_state). */ + +class riscv_hwbp final +{ + CORE_ADDR p_addr; + target_hw_bp_type p_type; + unsigned p_len; + unsigned ref_count; + +public: + riscv_hwbp (CORE_ADDR addr = 0, target_hw_bp_type type = hw_execute, + unsigned length = 0) noexcept; + void set_point (CORE_ADDR addr, target_hw_bp_type type = hw_access, + unsigned length = 0) noexcept; + [[nodiscard]] target_hw_bp_type type () const noexcept; + [[nodiscard]] unsigned type_for_dbg_reg () const; + [[nodiscard]] CORE_ADDR addr () const noexcept; + [[nodiscard]] unsigned len () const noexcept; + [[nodiscard]] bool is_wp () const noexcept; + [[nodiscard]] bool is_wp_at_addr (CORE_ADDR addr) const noexcept; + [[nodiscard]] bool is_busy () const noexcept; + [[nodiscard]] bool is_aligned () const noexcept; + void clear () noexcept; + [[nodiscard]] bool operator== (const riscv_hwbp &other) const noexcept; + [[nodiscard]] unsigned n_refs () const noexcept; + void inc_ref_count () noexcept; + void dec_ref_count () noexcept; +}; + +/* This enum is use to determine if we want to insert or to remove point. + See riscv_debug_reg_state::handle_point. */ +enum class point_action +{ + REMOVE, + INSERT +}; + +/* State of debug registers for a thread. */ + +class riscv_debug_reg_state final +{ +public: + /* Max number of hardware breakpoints + watchpoints. */ + static constexpr int riscv_hwbp_max_num = 16; + /* Suppose all CPUs that support hw bp also support compressed isa. */ + static constexpr int riscv_hwbp_alignment = 2; + /* Debug spec 1.0 has no restrictions on watchpoint alignment. */ + static constexpr int riscv_hwwp_alignment = 1; + + /* Method for handling action on breakpoint/watchpoint. */ + bool handle_point (const riscv_hwbp &point, point_action action); + /* Method for breakpoint/watchpoint insertion. */ + bool insert_one_point (const riscv_hwbp &point); + /* Method for breakpoint/watchpoint removal. */ + bool remove_one_point (const riscv_hwbp &point); + /* Method for notifying each thread that register with number idx needs to be + updated. Actual debug regs update will be performed when the is resumed. + */ + void notify_debug_reg_change (unsigned idx) const; + /* Helper method for debug printing. */ + void show_debug_regs (const char *func, const riscv_hwbp &point) const; + /* Method for getting available number of debug regs to use. */ + void linux_get_debug_reg_capacity (int tid); + /* Method for setting debug regs. */ + void linux_set_debug_regs (int tid); + /* Method for checking if any debug register is set. */ + [[nodiscard]] bool linux_any_set_debug_regs (); + /* Method for getting available number of debug regs to use. */ + [[nodiscard]] unsigned num_hwbp_regs () const noexcept; + /* Method for checking if wachtpoint is set at address. */ + [[nodiscard]] bool has_wp_at_addr (CORE_ADDR addr) const; + /* Method for checking if wachtpoint is set at address. */ + [[nodiscard]] CORE_ADDR wp_orig_addr (CORE_ADDR addr) const; + +private: + /* Current number of available debug registers to use. */ + unsigned riscv_num_hwbp_regs = 0; + /* Array with breakpoints/watchpoints. */ + std::array dr_bp_states; +}; + +/* Per-process data. Here state of debug registers for each process is stored. + All info about processes is stored in a list so it can be inserted and + removed easily. */ + +class riscv_process_info final +{ + riscv_process_info *next; + pid_t pid; + riscv_debug_reg_state state; + +public: + riscv_process_info (pid_t p, + riscv_process_info *to_be_next = nullptr) noexcept; + /* Get debug registers state for process. */ + [[nodiscard]] riscv_debug_reg_state &get_state () noexcept; + + static riscv_process_info &add_process_info (pid_t pid); + static void forget_process (pid_t pid) noexcept; + /* Get process info for process pid. If there is no such process info, + creates it and adds to process info list. */ + [[nodiscard]] static riscv_process_info &get_process_info (pid_t pid); + /* Get debug register state for process pid. If there is no such process + info, creates it and adds to process info list. */ + [[nodiscard]] static riscv_debug_reg_state &get_state (pid_t pid); + /* Get number of available debug registers to use for process pid and writes + result to debug register state accociate with this process. If there is no + such process info, creates it and adds to process info list. */ + static void linux_get_debug_reg_capacity (pid_t pid); +}; + +#endif // NAT_RISCV_LINUX_HW_POINT_H diff --git a/gdb/nat/riscv-linux.c b/gdb/nat/riscv-linux.c new file mode 100644 index 0000000000..51833abb22 --- /dev/null +++ b/gdb/nat/riscv-linux.c @@ -0,0 +1,78 @@ +/* Copyright (C) 2022 Free Software Foundation, Inc. + Contributed by Syntacore + + 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 "riscv-linux.h" + +#include "nat/linux-nat.h" +#include "nat/riscv-linux-hw-point.h" + +/* Called when resuming a thread LWP. + The hardware debug registers are updated when there is any change. */ + +void +riscv_linux_prepare_to_resume (lwp_info *lwp) +{ + arch_lwp_info *info = lwp_arch_private_info (lwp); + + /* NULL means this is the main thread still going through the shell, + or, no watchpoint has been set yet. In that case, there's + nothing to do. */ + if (info == nullptr) + return; + + if (info->has_changed ()) + { + ptid_t ptid = ptid_of_lwp (lwp); + int tid = ptid.lwp (); + riscv_debug_reg_state &state + = riscv_process_info::get_state (ptid.pid ()); + + if (show_debug_regs) + debug_printf ("prepare_to_resume thread %d\n", tid); + + state.linux_set_debug_regs (tid); + info->clear_changed (); + } +} + +/* Function to call when a new thread is detected. */ + +void +riscv_linux_new_thread (lwp_info *lwp) +{ + ptid_t ptid = ptid_of_lwp (lwp); + riscv_debug_reg_state &state = riscv_process_info::get_state (ptid.pid ()); + arch_lwp_info *info = new arch_lwp_info; + + /* If there are hardware breakpoints/watchpoints in the process then mark + that all the hardware breakpoint/watchpoint register pairs for this thread + need to be initialized (with data from + aarch_process_info.debug_reg_state). */ + if (state.linux_any_set_debug_regs ()) + info->mark_all_changed (state.num_hwbp_regs ()); + + lwp_set_arch_private_info (lwp, info); +} + +/* See nat/riscv-linux.h. */ + +void +riscv_linux_delete_thread (arch_lwp_info *arch_lwp) noexcept +{ + delete arch_lwp; +} diff --git a/gdb/nat/riscv-linux.h b/gdb/nat/riscv-linux.h new file mode 100644 index 0000000000..bbf7076a80 --- /dev/null +++ b/gdb/nat/riscv-linux.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2022 Free Software Foundation, Inc. + Contributed by Syntacore + + 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_RISCV_LINUX_H +#define NAT_RISCV_LINUX_H + +/* Function to call before a thread will be resumed. */ +void riscv_linux_prepare_to_resume (struct lwp_info *lwp); + +/* Function to call when a thread is being created. */ +void riscv_linux_new_thread (struct lwp_info *lwp); + +/* Function to call when a thread is being deleted. */ +void riscv_linux_delete_thread (struct arch_lwp_info *arch_lwp) noexcept; + +#endif // NAT_RISCV_LINUX_H diff --git a/gdb/riscv-linux-nat.c b/gdb/riscv-linux-nat.c index 7063494148..57dc930971 100644 --- a/gdb/riscv-linux-nat.c +++ b/gdb/riscv-linux-nat.c @@ -15,17 +15,22 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - #include "defs.h" -#include "regcache.h" + +#include "breakpoint.h" +#include "gdbcmd.h" #include "gregset.h" +#include "inferior.h" #include "linux-nat.h" +#include "regcache.h" #include "riscv-tdep.h" -#include "inferior.h" #include "elf/common.h" +#include "nat/gdb_ptrace.h" +#include "nat/riscv-linux-hw-point.h" #include "nat/riscv-linux-tdesc.h" +#include "nat/riscv-linux.h" #include @@ -45,6 +50,51 @@ class riscv_linux_nat_target final : public linux_nat_target /* Read suitable target description. */ const struct target_desc *read_description () override; + + /* Hardware breakpoint and watchpoint implementation. */ + int can_use_hw_breakpoint (enum bptype, int, int) override; + int insert_hw_breakpoint (struct gdbarch *, + struct bp_target_info *) override; + int remove_hw_breakpoint (struct gdbarch *, + struct bp_target_info *) override; + int insert_watchpoint (CORE_ADDR, int, enum target_hw_bp_type, + struct expression *) override; + int remove_watchpoint (CORE_ADDR, int, enum target_hw_bp_type, + struct expression *) override; + bool stopped_by_watchpoint () override; + bool stopped_data_address (CORE_ADDR *) override; + + /* Override the GNU/Linux inferior startup hook. */ + void post_startup_inferior (ptid_t) override; + + /* Override the GNU/Linux post attach hook. */ + void post_attach (int pid) override; + + /* Override the GNU/Linux thread creation hook. */ + void + low_new_thread (struct lwp_info *lp) override + { + riscv_linux_new_thread (lp); + } + + /* Override the GNU/Linux thread deletiong hook. */ + void + low_delete_thread (struct arch_lwp_info *lp) override + { + riscv_linux_delete_thread (lp); + } + + /* Override the GNU/Linux thread resuming hook. */ + void + low_prepare_to_resume (struct lwp_info *lp) override + { + riscv_linux_prepare_to_resume (lp); + } + + /* Override the GNU/Linux fork creation hook. */ + void low_new_fork (struct lwp_info *parent, pid_t child_pid) override; + /* Override the GNU/Linux process release hook. */ + void low_forget_process (pid_t pid) override; }; static riscv_linux_nat_target the_riscv_linux_nat_target; @@ -325,12 +375,287 @@ riscv_linux_nat_target::store_registers (struct regcache *regcache, int regnum) now. */ } +/* Check if nat target can use hardware breakpoints/watchpoints. */ + +int +riscv_linux_nat_target::can_use_hw_breakpoint (enum bptype type, int cnt, + int othertype) +{ + switch (type) + { + case bp_hardware_breakpoint: + case bp_hardware_watchpoint: + case bp_read_watchpoint: + case bp_access_watchpoint: + case bp_watchpoint: + if (riscv_process_info::get_state (inferior_ptid.pid ()).num_hwbp_regs () + == 0) + return 0; + break; + default: + gdb_assert_not_reached ("unexpected breakpoint type"); + } + return 1; +} + +/* Insert hardware breakpoint. Return 0 if point is inserted, -1 if not. */ + +int +riscv_linux_nat_target::insert_hw_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + int ret, len; + CORE_ADDR addr = bp_tgt->placed_address = bp_tgt->reqstd_address; + const enum target_hw_bp_type type = hw_execute; + riscv_debug_reg_state &state + = riscv_process_info::get_state (inferior_ptid.pid ()); + + gdbarch_breakpoint_from_pc (gdbarch, &addr, &len); + if (len < 0) + return -1; + + if (show_debug_regs) + fprintf_unfiltered ( + gdb_stdlog, "insert_point on entry (addr=0x%08" PRIx64 ", len=%d)\n", + addr, len); + + /* static_cast here is OK because we checked that len is not less than 0 */ + riscv_hwbp point (addr, type, static_cast (len)); + ret = state.handle_point (point, point_action::INSERT) ? 0 : -1; + + if (show_debug_regs) + state.show_debug_regs ("riscv_target::low_insert_point", point); + + return ret; +} + +/* Remove hardware breakpoint. Return 0 if point is removed, -1 if not. */ + +int +riscv_linux_nat_target::remove_hw_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + int ret, len; + CORE_ADDR addr = bp_tgt->placed_address; + const enum target_hw_bp_type type = hw_execute; + riscv_debug_reg_state &state + = riscv_process_info::get_state (inferior_ptid.pid ()); + + gdbarch_breakpoint_from_pc (gdbarch, &addr, &len); + if (len < 0) + return -1; + + if (show_debug_regs) + fprintf_unfiltered (gdb_stdlog, + "remove_hw_breakpoint on entry (addr=0x%08" PRIx64 + ", len=%d)\n", + addr, len); + + /* static_cast here is OK because we checked that len is not less than 0 */ + riscv_hwbp point (addr, type, static_cast (len)); + ret = state.handle_point (point, point_action::REMOVE) ? 0 : -1; + + if (show_debug_regs) + state.show_debug_regs ("riscv_linux_nat_target::remove_hw_breakpoint", + point); + + return ret; +} + +/* Insert hardware watchpoint of type TYPE on region starting at address ADDR + with LEN legth. Return 0 if point is inserted, -1 if not. */ + +int +riscv_linux_nat_target::insert_watchpoint (CORE_ADDR addr, int len, + enum target_hw_bp_type type, + struct expression *cond) +{ + int ret; + + gdb_assert (type != hw_execute); + if (len < 0) + return -1; + + riscv_debug_reg_state &state + = riscv_process_info::get_state (inferior_ptid.pid ()); + + if (show_debug_regs) + fprintf_unfiltered (gdb_stdlog, + "insert_watchpoint on entry (addr=0x%08" PRIx64 + ", len=%d)\n", + addr, len); + + /* static_cast here is OK because we checked that len is not less than 0 */ + riscv_hwbp point (addr, type, static_cast (len)); + ret = state.handle_point (point, point_action::INSERT) ? 0 : -1; + + if (show_debug_regs) + state.show_debug_regs ("riscv_linux_nat_target::insert_watchpoint", point); + + return ret; +} + +/* Remove hardware watchpoint of type TYPE on region starting at address ADDR + with LEN legth. Return 0 if point is removed, -1 if not. */ + +int +riscv_linux_nat_target::remove_watchpoint (CORE_ADDR addr, int len, + enum target_hw_bp_type type, + struct expression *cond) +{ + int ret; + + gdb_assert (type != hw_execute); + if (len < 0) + return -1; + + riscv_debug_reg_state &state + = riscv_process_info::get_state (inferior_ptid.pid ()); + + if (show_debug_regs) + fprintf_unfiltered (gdb_stdlog, + "remove_watchpoint on entry (addr=0x%08" PRIx64 + ", len=%d)\n", + addr, len); + + /* static_cast here is OK because we checked that len is not less than 0 */ + riscv_hwbp point (addr, type, static_cast (len)); + ret = state.handle_point (point, point_action::REMOVE) ? 0 : -1; + + if (show_debug_regs) + state.show_debug_regs ("riscv_linux_nat_target::remove_watchpoint", point); + + return ret; +} + +/* Implement the "stopped_data_address" target_ops method. */ + +bool +riscv_linux_nat_target::stopped_data_address (CORE_ADDR *addr_p) +{ + siginfo_t siginfo; + + /* Get the siginfo. */ + if (linux_nat_get_siginfo (inferior_ptid, &siginfo) == 0) + { + *addr_p = 0; + return false; + } + + /* Need to be a hardware breakpoint/watchpoint trap. */ + if (siginfo.si_signo != SIGTRAP || (siginfo.si_code != TRAP_HWBKPT)) + { + *addr_p = 0; + return false; + } + + /* Get address that caused stop. */ + const CORE_ADDR addr_trap = reinterpret_cast (siginfo.si_addr); + + riscv_debug_reg_state &state + = riscv_process_info::get_state (inferior_ptid.pid ()); + + /* Check if the address matches any watched address. */ + if (state.has_wp_at_addr (addr_trap)) + { + CORE_ADDR addr = state.wp_orig_addr (addr_trap); + *addr_p = addr; + return true; + } + + *addr_p = 0; + return false; +} + +/* Implement the "stopped_by_watchpoint" target_ops method. */ + +bool +riscv_linux_nat_target::stopped_by_watchpoint () +{ + CORE_ADDR addr; + return stopped_data_address (&addr); +} + +/* Implement the virtual inf_ptrace_target::post_startup_inferior method. */ + +void +riscv_linux_nat_target::post_startup_inferior (ptid_t ptid) +{ + low_forget_process (ptid.pid ()); + riscv_process_info::linux_get_debug_reg_capacity (ptid.pid ()); + linux_nat_target::post_startup_inferior (ptid); +} + +/* Implement the "post_attach" target_ops method. */ + +void +riscv_linux_nat_target::post_attach (int pid) +{ + low_forget_process (pid); + riscv_process_info::linux_get_debug_reg_capacity (pid); + linux_nat_target::post_attach (pid); +} + +/* linux_nat_new_fork hook. */ + +void +riscv_linux_nat_target::low_new_fork (struct lwp_info *parent, pid_t child_pid) +{ + pid_t parent_pid; + + /* NULL means no watchpoint has ever been set in the parent. In + that case, there's nothing to do. */ + if (parent->arch_private == NULL) + return; + + /* Copy all debug registers state from parent to child process. */ + + parent_pid = parent->ptid.pid (); + riscv_debug_reg_state &parent_state + = riscv_process_info::get_state (parent_pid); + riscv_debug_reg_state &child_state + = riscv_process_info::get_state (child_pid); + child_state = parent_state; +} + +/* Called whenever GDB is no longer debugging process PID. It deletes + data structures that keep track of debug register state. */ + +void +riscv_linux_nat_target::low_forget_process (pid_t pid) +{ + riscv_process_info::forget_process (pid); +} + +/* Define riscv maintenance commands. */ + +static void +add_show_debug_regs_command (void) +{ + /* A maintenance command to enable printing the internal DRi mirror + variables. */ + add_setshow_boolean_cmd ( + "show-debug-regs", class_maintenance, &show_debug_regs, _ ("\ +Set whether to show variables that mirror the riscv debug registers."), + _ ("\ +Show whether to show variables that mirror the riscv debug registers."), + _ ("\ +Use \"on\" to enable, \"off\" to disable.\n\ +If enabled, the debug registers values are shown when GDB inserts\n\ +or removes a hardware breakpoint or watchpoint, and when the inferior\n\ +triggers a breakpoint or watchpoint."), + NULL, NULL, &maintenance_set_cmdlist, &maintenance_show_cmdlist); +} + /* Initialize RISC-V Linux native support. */ void _initialize_riscv_linux_nat (); void _initialize_riscv_linux_nat () { + /* Register maintenance command. */ + add_show_debug_regs_command (); + /* Register the target. */ linux_target = &the_riscv_linux_nat_target; add_inf_child_target (&the_riscv_linux_nat_target); diff --git a/gdbserver/configure.srv b/gdbserver/configure.srv index 6e09b0eeb7..5790936f7c 100644 --- a/gdbserver/configure.srv +++ b/gdbserver/configure.srv @@ -253,6 +253,8 @@ case "${gdbserver_host}" in ipa_obj="${ipa_ppc_linux_regobj} linux-ppc-ipa.o" ;; riscv*-*-linux*) srv_tgtobj="arch/riscv.o nat/riscv-linux-tdesc.o" + srv_tgtobj="$srv_tgtobj nat/riscv-linux-hw-point.o" + srv_tgtobj="$srv_tgtobj nat/riscv-linux.o" srv_tgtobj="${srv_tgtobj} linux-riscv-low.o" srv_tgtobj="${srv_tgtobj} ${srv_linux_obj}" srv_linux_regsets=yes diff --git a/gdbserver/linux-riscv-low.cc b/gdbserver/linux-riscv-low.cc index 6b2902e422..3ed7d282d9 100644 --- a/gdbserver/linux-riscv-low.cc +++ b/gdbserver/linux-riscv-low.cc @@ -21,7 +21,10 @@ #include "linux-low.h" #include "tdesc.h" + #include "elf/common.h" +#include "nat/riscv-linux.h" +#include "nat/riscv-linux-hw-point.h" #include "nat/riscv-linux-tdesc.h" #include "opcode/riscv.h" @@ -42,6 +45,8 @@ public: const gdb_byte *sw_breakpoint_from_kind (int kind, int *size) override; + bool supports_z_point_type (char z_type) override; + protected: void low_arch_setup () override; @@ -59,6 +64,26 @@ protected: void low_set_pc (regcache *regcache, CORE_ADDR newpc) override; bool low_breakpoint_at (CORE_ADDR pc) override; + + int low_insert_point (raw_bkpt_type type, CORE_ADDR addr, int size, + raw_breakpoint *bp) override; + + int low_remove_point (raw_bkpt_type type, CORE_ADDR addr, int size, + raw_breakpoint *bp) override; + + bool low_stopped_by_watchpoint () override; + + CORE_ADDR low_stopped_data_address () override; + + arch_process_info *low_new_process () override; + + void low_delete_process (arch_process_info *info) override; + + void low_new_thread (lwp_info *) override; + + void low_delete_thread (arch_lwp_info *) override; + + void low_prepare_to_resume (lwp_info *lwp) override; }; /* The singleton target ops object. */ @@ -79,6 +104,63 @@ riscv_target::low_cannot_store_register (int regno) "is not implemented by the target"); } +void +riscv_target::low_prepare_to_resume (lwp_info *lwp) +{ + riscv_linux_prepare_to_resume (lwp); +} + +/* Per-process arch-specific data we want to keep. */ + +struct arch_process_info +{ + /* Hardware breakpoint/watchpoint data. + The reason for them to be per-process rather than per-thread is + due to the lack of information in the gdbserver environment; + gdbserver is not told that whether a requested hardware + breakpoint/watchpoint is thread specific or not, so it has to set + each hw bp/wp for every thread in the current process. The + higher level bp/wp management in gdb will resume a thread if a hw + bp/wp trap is not expected for it. Since the hw bp/wp setting is + same for each thread, it is reasonable for the data to live here. + */ + riscv_debug_reg_state debug_reg_state; +}; + +/* Implementation of linux target ops method "low_new_process". */ + +arch_process_info * +riscv_target::low_new_process () +{ + arch_process_info *info = new arch_process_info; + + return info; +} + +/* Implementation of linux target ops method "low_delete_process". */ + +void +riscv_target::low_delete_process (arch_process_info *info) +{ + delete info; +} + +/* Implementation of linux target ops method "low_new_thread". */ + +void +riscv_target::low_new_thread (lwp_info *lwp) +{ + riscv_linux_new_thread (lwp); +} + +/* Implementation of linux target ops method "low_delete_thread". */ + +void +riscv_target::low_delete_thread (arch_lwp_info *arch_lwp) +{ + riscv_linux_delete_thread (arch_lwp); +} + /* Implementation of linux target ops method "low_arch_setup". */ void @@ -93,6 +175,8 @@ riscv_target::low_arch_setup () if (!tdesc->expedite_regs) init_target_desc (tdesc.get (), expedite_regs); current_process ()->tdesc = tdesc.release (); + + riscv_process_info::linux_get_debug_reg_capacity (lwpid_of (current_thread)); } /* Collect GPRs from REGCACHE into BUF. */ @@ -285,6 +369,24 @@ riscv_target::sw_breakpoint_from_kind (int kind, int *size) } } +/* Implementation of target ops method "supports_z_point_type". */ + +bool +riscv_target::supports_z_point_type (char z_type) +{ + switch (z_type) + { + case Z_PACKET_SW_BP: + case Z_PACKET_HW_BP: + case Z_PACKET_WRITE_WP: + case Z_PACKET_READ_WP: + case Z_PACKET_ACCESS_WP: + return true; + default: + return false; + } +} + /* Implementation of linux target ops method "low_breakpoint_at". */ bool @@ -308,6 +410,110 @@ riscv_target::low_breakpoint_at (CORE_ADDR pc) return false; } +/* Implementation of linux target ops method "low_insert_point". + + It actually only records the info of the to-be-inserted bp/wp; + the actual insertion will happen when threads are resumed. */ + +int +riscv_target::low_insert_point (raw_bkpt_type type, CORE_ADDR addr, int len, + raw_breakpoint *bp) +{ + int ret; + enum target_hw_bp_type targ_type; + + if (len < 0) + return -1; + + riscv_debug_reg_state &state + = riscv_process_info::get_state (pid_of (current_thread)); + + if (show_debug_regs) + fprintf (stderr, "insert_point on entry (addr=0x%08" PRIx64 ", len=%d)\n", + addr, len); + + /* Determine the type from the raw breakpoint type. */ + targ_type = raw_bkpt_type_to_target_hw_bp_type (type); + /* static_cast here is OK because we checked that len is not less than 0 */ + riscv_hwbp point (addr, targ_type, static_cast (len)); + ret = state.handle_point (point, point_action::INSERT) ? 0 : -1; + + if (show_debug_regs) + state.show_debug_regs ("riscv_target::low_insert_point", point); + + return ret; +} + +/* Implementation of linux target ops method "low_remove_point". + + It actually only records the info of the to-be-removed bp/wp; + the actual remove will happen when threads are resumed. */ + +int +riscv_target::low_remove_point (raw_bkpt_type type, CORE_ADDR addr, int len, + raw_breakpoint *bp) +{ + int ret; + enum target_hw_bp_type targ_type; + + if (len < 0) + return -1; + + riscv_debug_reg_state &state + = riscv_process_info::get_state (pid_of (current_thread)); + + if (show_debug_regs) + fprintf (stderr, "remove_point on entry (addr=0x%08" PRIx64 ", len=%d)\n", + addr, len); + + /* Determine the type from the raw breakpoint type. */ + targ_type = raw_bkpt_type_to_target_hw_bp_type (type); + /* static_cast here is OK because we checked that len is not less than 0 */ + riscv_hwbp point (addr, targ_type, static_cast (len)); + ret = state.handle_point (point, point_action::REMOVE) ? 0 : -1; + + if (show_debug_regs) + state.show_debug_regs ("riscv_target::low_remove_point", point); + + return ret; +} + +/* Implementation of linux target ops method "low_stopped_data_address". */ + +CORE_ADDR +riscv_target::low_stopped_data_address () +{ + siginfo_t siginfo; + int pid = lwpid_of (current_thread); + + /* Get the siginfo. */ + if (ptrace (PTRACE_GETSIGINFO, pid, NULL, &siginfo) != 0) + return static_cast (0); + + /* Need to be a hardware breakpoint/watchpoint trap. */ + if (siginfo.si_signo != SIGTRAP || (siginfo.si_code != TRAP_HWBKPT)) + return static_cast (0); + + /* Make sure to ignore the top byte, otherwise we may not recognize a + hardware watchpoint hit. The stopped data addresses coming from the + kernel can potentially be tagged addresses. */ + const CORE_ADDR addr_trap = reinterpret_cast (siginfo.si_addr); + + /* Check if the address matches any watched address. */ + riscv_debug_reg_state &state + = riscv_process_info::get_state (pid_of (current_thread)); + + return state.wp_orig_addr (addr_trap); +} + +/* Implementation of linux target ops method "low_stopped_by_watchpoint". */ + +bool +riscv_target::low_stopped_by_watchpoint () +{ + return (low_stopped_data_address () != 0); +} + /* The linux target ops object. */ linux_process_target *the_linux_target = &the_riscv_target;