@@ -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 \
@@ -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)
new file mode 100644
@@ -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 <http://www.gnu.org/licenses/>. */
+#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 <sys/uio.h>
+
+/* The order in which <sys/ptrace.h> and <asm/ptrace.h> are included
+ can be important. <sys/ptrace.h> often declares various PTRACE_*
+ enums. <asm/ptrace.h> often defines preprocessor constants for
+ these very same symbols. When that's the case, build errors will
+ result when <asm/ptrace.h> is included before <sys/ptrace.h>. */
+#include <sys/ptrace.h>
+#include <asm/ptrace.h>
+
+#include <algorithm>
+
+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<CORE_ADDR> (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);
+}
new file mode 100644
@@ -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 <http://www.gnu.org/licenses/>. */
+
+#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 <array>
+#include <cstdint>
+
+/* 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<riscv_hwbp, riscv_hwbp_max_num> 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
new file mode 100644
@@ -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 <http://www.gnu.org/licenses/>. */
+#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;
+}
new file mode 100644
@@ -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 <http://www.gnu.org/licenses/>. */
+#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
@@ -15,17 +15,22 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
#include "defs.h"
-#include "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 <sys/ptrace.h>
@@ -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<unsigned> (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<unsigned> (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<unsigned> (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<unsigned> (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<CORE_ADDR> (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);
@@ -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
@@ -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<unsigned> (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<unsigned> (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<CORE_ADDR> (0);
+
+ /* Need to be a hardware breakpoint/watchpoint trap. */
+ if (siginfo.si_signo != SIGTRAP || (siginfo.si_code != TRAP_HWBKPT))
+ return static_cast<CORE_ADDR> (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<CORE_ADDR> (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;