aboutsummaryrefslogtreecommitdiff
path: root/gdb/nat
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/nat')
-rw-r--r--gdb/nat/loongarch-hw-point.c293
-rw-r--r--gdb/nat/loongarch-hw-point.h92
-rw-r--r--gdb/nat/loongarch-linux-hw-point.c227
-rw-r--r--gdb/nat/loongarch-linux-hw-point.h125
-rw-r--r--gdb/nat/loongarch-linux.c87
-rw-r--r--gdb/nat/loongarch-linux.h42
6 files changed, 866 insertions, 0 deletions
diff --git a/gdb/nat/loongarch-hw-point.c b/gdb/nat/loongarch-hw-point.c
new file mode 100644
index 0000000..44af61a
--- /dev/null
+++ b/gdb/nat/loongarch-hw-point.c
@@ -0,0 +1,293 @@
+/* Native-dependent code for GNU/Linux on LoongArch processors.
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Contributed by Loongson Ltd.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "gdbsupport/common-defs.h"
+#include "gdbsupport/break-common.h"
+#include "gdbsupport/common-regcache.h"
+#include "loongarch-hw-point.h"
+#include "loongarch-linux-hw-point.h"
+
+/* Number of hardware breakpoints/watchpoints the target supports.
+ They are initialized with values obtained via ptrace. */
+
+int loongarch_num_wp_regs;
+
+/* Given the hardware breakpoint or watchpoint type TYPE and its
+ length LEN, return the expected encoding for a hardware
+ breakpoint/watchpoint control register. */
+
+static unsigned int
+loongarch_point_encode_ctrl_reg (enum target_hw_bp_type type, int len)
+{
+ unsigned int ctrl, ttype, llen;
+
+ gdb_assert (len <= LOONGARCH_HWP_MAX_LEN_PER_REG);
+
+ /* type */
+ switch (type)
+ {
+ case hw_write:
+ ttype = 2;
+ break;
+ case hw_read:
+ ttype = 1;
+ break;
+ case hw_access:
+ ttype = 3;
+ break;
+ case hw_execute:
+ ttype = 0;
+ break;
+ default:
+ perror_with_name (_("Unrecognized watchpoint type"));
+ }
+
+ /* len */
+ switch (len)
+ {
+ case 1:
+ llen = 0b11;
+ break;
+ case 2:
+ llen = 0b10;
+ break;
+ case 4:
+ llen = 0b01;
+ break;
+ case 8:
+ llen = 0b00;
+ break;
+ default:
+ perror_with_name (_("Unrecognized watchpoint length"));
+ }
+ ctrl = 0;
+ if (type != hw_execute) {
+ /* type and length bitmask */
+ ctrl |= llen << 10;
+ ctrl |= ttype << 8;
+ }
+ ctrl |= CTRL_PLV3_ENABLE;
+ return ctrl;
+}
+
+
+/* Record the insertion of one breakpoint/watchpoint, as represented
+ by ADDR and CTRL, in the process' arch-specific data area *STATE. */
+
+static int
+loongarch_dr_state_insert_one_point (ptid_t ptid,
+ struct loongarch_debug_reg_state *state,
+ enum target_hw_bp_type type, CORE_ADDR addr,
+ int len, CORE_ADDR addr_orig)
+{
+ int i, idx, num_regs, is_watchpoint;
+ unsigned int ctrl, *dr_ctrl_p, *dr_ref_count;
+ CORE_ADDR *dr_addr_p;
+
+ /* Set up state pointers. */
+ is_watchpoint = (type != hw_execute);
+ if (is_watchpoint)
+ {
+ num_regs = loongarch_num_wp_regs;
+ dr_addr_p = state->dr_addr_wp;
+ dr_ctrl_p = state->dr_ctrl_wp;
+ dr_ref_count = state->dr_ref_count_wp;
+ }
+ else
+ {
+ return -1;
+ }
+
+ ctrl = loongarch_point_encode_ctrl_reg (type, len);
+
+ /* Find an existing or free register in our cache. */
+ idx = -1;
+ for (i = 0; i < num_regs; ++i)
+ {
+ if ((dr_ctrl_p[i] & CTRL_PLV3_ENABLE) == 0) // PLV3 disable
+ {
+ gdb_assert (dr_ref_count[i] == 0);
+ idx = i;
+ /* no break; continue hunting for an exising one. */
+ }
+ else if (dr_addr_p[i] == addr && dr_ctrl_p[i] == ctrl)
+ {
+ idx = i;
+ gdb_assert (dr_ref_count[i] != 0);
+ break;
+ }
+
+ }
+
+ /* No space. */
+ if (idx == -1)
+ return -1;
+
+ /* Update our cache. */
+ if ((dr_ctrl_p[idx] & CTRL_PLV3_ENABLE) == 0)
+ {
+ /* new entry */
+ dr_addr_p[idx] = addr;
+ dr_ctrl_p[idx] = ctrl;
+ dr_ref_count[idx] = 1;
+
+ /* Notify the change. */
+ loongarch_notify_debug_reg_change (ptid, is_watchpoint, idx);
+ }
+ else
+ {
+ /* existing entry */
+ dr_ref_count[idx]++;
+ }
+
+ return 0;
+}
+
+/* Record the removal of one breakpoint/watchpoint, as represented by
+ ADDR and CTRL, in the process' arch-specific data area *STATE. */
+
+static int
+loongarch_dr_state_remove_one_point (ptid_t ptid,
+ struct loongarch_debug_reg_state *state,
+ enum target_hw_bp_type type, CORE_ADDR addr,
+ int len, CORE_ADDR addr_orig)
+{
+ int i, num_regs, is_watchpoint;
+ unsigned int ctrl, *dr_ctrl_p, *dr_ref_count;
+ CORE_ADDR *dr_addr_p;
+
+ /* Set up state pointers. */
+ is_watchpoint = (type != hw_execute);
+ if (is_watchpoint)
+ {
+ num_regs = loongarch_num_wp_regs;
+ dr_addr_p = state->dr_addr_wp;
+ dr_ctrl_p = state->dr_ctrl_wp;
+ dr_ref_count = state->dr_ref_count_wp;
+ }
+ else
+ {
+ return -1;
+ }
+
+ ctrl = loongarch_point_encode_ctrl_reg (type, len);
+
+ /* Find the entry that matches the ADDR and CTRL. */
+ for (i = 0; i < num_regs; ++i)
+ if (dr_addr_p[i] == addr && dr_ctrl_p[i] == ctrl)
+ {
+ gdb_assert (dr_ref_count[i] != 0);
+ break;
+ }
+
+ /* Not found. */
+ if (i == num_regs)
+ return -1;
+
+ /* Clear our cache. */
+ if (--dr_ref_count[i] == 0)
+ {
+ dr_addr_p[i] = 0;
+ dr_ctrl_p[i] = 0;
+
+ /* Notify the change. */
+ loongarch_notify_debug_reg_change (ptid, is_watchpoint, i);
+ }
+
+ return 0;
+}
+
+int
+loongarch_handle_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr,
+ int len, int is_insert, ptid_t ptid,
+ struct loongarch_debug_reg_state *state)
+{
+ if (is_insert)
+ return loongarch_dr_state_insert_one_point (ptid, state, type, addr,
+ len, addr);
+ else
+ return loongarch_dr_state_remove_one_point (ptid, state, type, addr,
+ len, addr);
+}
+
+
+/* See nat/loongarch-hw-point.h. */
+
+bool
+loongarch_any_set_debug_regs_state (loongarch_debug_reg_state *state,
+ bool watchpoint)
+{
+ int count = watchpoint ? loongarch_num_wp_regs : 0;
+ if (count == 0)
+ return false;
+
+ const CORE_ADDR *addr = watchpoint ? state->dr_addr_wp : 0;
+ const unsigned int *ctrl = watchpoint ? state->dr_ctrl_wp : 0;
+
+ for (int i = 0; i < count; i++)
+ if (addr[i] != 0 || ctrl[i] != 0)
+ return true;
+
+ return false;
+}
+
+/* Print the values of the cached breakpoint/watchpoint registers. */
+
+void
+loongarch_show_debug_reg_state (struct loongarch_debug_reg_state *state,
+ const char *func, CORE_ADDR addr,
+ int len, enum target_hw_bp_type type)
+{
+ int i;
+
+ debug_printf ("%s", func);
+ if (addr || len)
+ debug_printf (" (addr=0x%08lx, len=%d, type=%s)",
+ (unsigned long) addr, len,
+ type == hw_write ? "hw-write-watchpoint"
+ : (type == hw_read ? "hw-read-watchpoint"
+ : (type == hw_access ? "hw-access-watchpoint"
+ : (type == hw_execute ? "hw-breakpoint"
+ : "??unknown??"))));
+ debug_printf (":\n");
+
+ debug_printf ("\tWATCHPOINTs:\n");
+ for (i = 0; i < loongarch_num_wp_regs; i++)
+ debug_printf ("\tWP%d: addr=%s, ctrl=0x%08x, ref.count=%d\n",
+ i, core_addr_to_string_nz (state->dr_addr_wp[i]),
+ state->dr_ctrl_wp[i], state->dr_ref_count_wp[i]);
+}
+
+/* Return true if we can watch a memory region that starts address
+ ADDR and whose length is LEN in bytes. */
+
+int
+loongarch_region_ok_for_watchpoint (CORE_ADDR addr, int len)
+{
+ /* Can not set watchpoints for zero or negative lengths. */
+ if (len <= 0)
+ return 0;
+
+ /* Must have hardware watchpoint debug register(s). */
+ if (loongarch_num_wp_regs == 0)
+ return 0;
+
+ return 1;
+}
diff --git a/gdb/nat/loongarch-hw-point.h b/gdb/nat/loongarch-hw-point.h
new file mode 100644
index 0000000..86e5aa3
--- /dev/null
+++ b/gdb/nat/loongarch-hw-point.h
@@ -0,0 +1,92 @@
+/* Native-dependent code for GNU/Linux on LoongArch processors.
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Contributed by Loongson Ltd.
+
+ 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_LOONGARCH_HW_POINT_H
+#define NAT_LOONGARCH_HW_POINT_H
+
+/* Macro definitions, data structures, and code for the hardware
+ breakpoint and hardware watchpoint support follow. We use the
+ following abbreviations throughout the code:
+
+ hw - hardware
+ bp - breakpoint
+ wp - watchpoint */
+
+/* Maximum number of hardware breakpoint and watchpoint registers.
+ Neither of these values may exceed the width of dr_changed_t
+ measured in bits. */
+
+#define LOONGARCH_HWP_MAX_NUM 8
+
+
+/* The maximum length of a memory region that can be watched by one
+ hardware watchpoint register. */
+
+#define LOONGARCH_HWP_MAX_LEN_PER_REG 8
+#define CTRL_PLV3_ENABLE 0x10
+
+#define DR_CONTROL_ENABLED(ctrl) ((ctrl & CTRL_PLV3_ENABLE) == CTRL_PLV3_ENABLE)
+
+/* Structure for managing the hardware breakpoint/watchpoint resources.
+ DR_ADDR_* stores the address, DR_CTRL_* stores the control register
+ content, and DR_REF_COUNT_* counts the numbers of references to the
+ corresponding bp/wp, by which way the limited hardware resources
+ are not wasted on duplicated bp/wp settings (though so far gdb has
+ done a good job by not sending duplicated bp/wp requests). */
+
+struct loongarch_debug_reg_state
+{
+ /* hardware watchpoint */
+ CORE_ADDR dr_addr_wp[LOONGARCH_HWP_MAX_NUM];
+ unsigned int dr_ctrl_wp[LOONGARCH_HWP_MAX_NUM];
+ unsigned int dr_ref_count_wp[LOONGARCH_HWP_MAX_NUM];
+};
+
+extern int loongarch_num_wp_regs;
+
+/* Invoked when IDXth breakpoint/watchpoint register pair needs to be
+ updated. */
+
+void loongarch_notify_debug_reg_change (ptid_t ptid, int is_watchpoint,
+ unsigned int idx);
+
+
+int loongarch_handle_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr,
+ int len, int is_insert, ptid_t ptid,
+ struct loongarch_debug_reg_state *state);
+
+/* Return TRUE if there are any hardware breakpoints. If WATCHPOINT is TRUE,
+ check hardware watchpoints instead. */
+
+bool loongarch_any_set_debug_regs_state (loongarch_debug_reg_state *state,
+ bool watchpoint);
+
+/* Print the values of the cached breakpoint/watchpoint registers. */
+
+void loongarch_show_debug_reg_state (struct loongarch_debug_reg_state *state,
+ const char *func, CORE_ADDR addr,
+ int len, enum target_hw_bp_type type);
+
+/* Return true if we can watch a memory region that starts address
+ ADDR and whose length is LEN in bytes. */
+
+int loongarch_region_ok_for_watchpoint (CORE_ADDR addr, int len);
+
+#endif /* NAT_LOONGARCH_HW_POINT_H */
diff --git a/gdb/nat/loongarch-linux-hw-point.c b/gdb/nat/loongarch-linux-hw-point.c
new file mode 100644
index 0000000..cced5a3
--- /dev/null
+++ b/gdb/nat/loongarch-linux-hw-point.c
@@ -0,0 +1,227 @@
+/* Native-dependent code for GNU/Linux on LoongArch processors.
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Contributed by Loongson Ltd.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "gdbsupport/common-defs.h"
+#include "gdbsupport/break-common.h"
+#include "gdbsupport/common-regcache.h"
+#include "nat/linux-nat.h"
+#include "loongarch-linux-hw-point.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 "elf/common.h"
+
+/* Hash table storing per-process data. We don't bind this to a
+ per-inferior registry because of targets like x86 GNU/Linux that
+ need to keep track of processes that aren't bound to any inferior
+ (e.g., fork children, checkpoints). */
+
+static std::unordered_map<pid_t, loongarch_debug_reg_state>
+loongarch_debug_process_state;
+
+/* See loongarch-linux-hw-point.h */
+
+/* Helper for loongarch_notify_debug_reg_change. Records the
+ information about the change of one hardware breakpoint/watchpoint
+ setting for the thread LWP.
+ N.B. The actual updating of hardware debug registers is not
+ carried out until the moment the thread is resumed. */
+
+static int
+loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint,
+ unsigned int idx)
+{
+ int tid = ptid_of_lwp (lwp).lwp ();
+ struct arch_lwp_info *info = lwp_arch_private_info (lwp);
+ dr_changed_t *dr_changed_ptr;
+ dr_changed_t dr_changed;
+
+ if (!is_watchpoint)
+ return -1;
+
+ if (info == NULL)
+ {
+ info = XCNEW (struct arch_lwp_info);
+ lwp_set_arch_private_info (lwp, info);
+ }
+
+ if (show_debug_regs)
+ {
+ debug_printf ("loongarch_dr_change_callback: \n\tOn entry:\n");
+ debug_printf ("\ttid%d, dr_changed_wp=0x%s\n",
+ tid, phex (info->dr_changed_wp, 8));
+ }
+
+ dr_changed_ptr = &info->dr_changed_wp;
+ dr_changed = *dr_changed_ptr;
+
+ gdb_assert (idx >= 0 && idx <= loongarch_num_wp_regs);
+
+ /* The actual update is done later just before resuming the lwp,
+ we just mark that one register pair needs updating. */
+ DR_MARK_N_CHANGED (dr_changed, idx);
+ *dr_changed_ptr = dr_changed;
+
+ /* 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_wp=0x%s\n",
+ tid, phex (info->dr_changed_wp, 8));
+ }
+
+ return 0;
+}
+
+/* Notify each thread that their IDXth breakpoint/watchpoint register
+ pair needs to be updated. The message will be recorded in each
+ thread's arch-specific data area, the actual updating will be done
+ when the thread is resumed. */
+
+void
+loongarch_notify_debug_reg_change (ptid_t ptid,
+ int is_watchpoint, unsigned int idx)
+{
+ ptid_t pid_ptid = ptid_t (ptid.pid ());
+
+ iterate_over_lwps (pid_ptid, [=] (struct lwp_info *info)
+ {
+ return loongarch_dr_change_callback (info,
+ is_watchpoint,
+ idx);
+ });
+}
+
+/* Call ptrace to set the thread TID's hardware breakpoint/watchpoint
+ registers with data from *STATE. */
+
+void
+loongarch_linux_set_debug_regs (struct loongarch_debug_reg_state *state,
+ int tid, int watchpoint)
+{
+ int i, count;
+ struct iovec iov;
+ struct loongarch_user_watch_state regs;
+ const CORE_ADDR *addr;
+ const unsigned int *ctrl;
+
+ memset (&regs, 0, sizeof (regs));
+ iov.iov_base = &regs;
+ count = watchpoint ? loongarch_num_wp_regs : 0;
+ addr = watchpoint ? state->dr_addr_wp : 0;
+ ctrl = watchpoint ? state->dr_ctrl_wp : 0;
+
+ if (count == 0)
+ return;
+
+ iov.iov_len = (offsetof (struct loongarch_user_watch_state, dbg_regs)
+ + count * sizeof (regs.dbg_regs[0]));
+ for (i = 0; i < count; i++)
+ {
+ regs.dbg_regs[i].addr = addr[i];
+ regs.dbg_regs[i].ctrl = ctrl[i];
+ }
+
+ if (ptrace(PTRACE_SETREGSET, tid, NT_LOONGARCH_HW_WATCH, (void *) &iov))
+ {
+ if (errno == EINVAL)
+ error (_("Invalid argument setting hardware debug registers"));
+ else
+ error (_("Unexpected error setting hardware debug registers"));
+ }
+
+}
+
+/* Get the hardware debug register capacity information from the
+ process represented by TID. */
+
+void
+loongarch_linux_get_debug_reg_capacity (int tid)
+{
+ struct iovec iov;
+ struct loongarch_user_watch_state dreg_state;
+ int result;
+ iov.iov_base = &dreg_state;
+ iov.iov_len = sizeof (dreg_state);
+
+ /* Get hardware watchpoint register info. */
+ result = ptrace (PTRACE_GETREGSET, tid, NT_LOONGARCH_HW_WATCH, &iov);
+
+ if (result == 0)
+ {
+ loongarch_num_wp_regs = LOONGARCH_DEBUG_NUM_SLOTS (dreg_state.dbg_info);
+ if (loongarch_num_wp_regs > LOONGARCH_HWP_MAX_NUM)
+ {
+ warning (_("Unexpected number of hardware watchpoint registers"
+ " reported by ptrace, got %d, expected %d."),
+ loongarch_num_wp_regs, LOONGARCH_HWP_MAX_NUM);
+ loongarch_num_wp_regs = LOONGARCH_HWP_MAX_NUM;
+ }
+ }
+ else
+ {
+ warning (_("Unable to determine the number of hardware watchpoints"
+ " available."));
+ loongarch_num_wp_regs = 0;
+ }
+
+}
+
+/* Return the debug register state for process PID. If no existing
+ state is found for this process, return nullptr. */
+
+struct loongarch_debug_reg_state *
+loongarch_lookup_debug_reg_state (pid_t pid)
+{
+ auto it = loongarch_debug_process_state.find (pid);
+ if (it != loongarch_debug_process_state.end ())
+ return &it->second;
+
+ return nullptr;
+}
+
+/* Return the debug register state for process PID. If no existing
+ state is found for this process, create new state. */
+
+struct loongarch_debug_reg_state *
+loongarch_get_debug_reg_state (pid_t pid)
+{
+ return &loongarch_debug_process_state[pid];
+}
+
+/* Remove any existing per-process debug state for process PID. */
+
+void
+loongarch_remove_debug_reg_state (pid_t pid)
+{
+ loongarch_debug_process_state.erase (pid);
+}
diff --git a/gdb/nat/loongarch-linux-hw-point.h b/gdb/nat/loongarch-linux-hw-point.h
new file mode 100644
index 0000000..4086907
--- /dev/null
+++ b/gdb/nat/loongarch-linux-hw-point.h
@@ -0,0 +1,125 @@
+/* Native-dependent code for GNU/Linux on LoongArch processors.
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Contributed by Loongson Ltd.
+
+ 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_LOONGARCH_LINUX_HW_POINT_H
+#define NAT_LOONGARCH_LINUX_HW_POINT_H
+
+#include "gdbsupport/break-common.h" /* For enum target_hw_bp_type. */
+
+#include "nat/loongarch-hw-point.h"
+
+struct loongarch_user_watch_state {
+ uint64_t dbg_info;
+ struct {
+ uint64_t addr;
+ uint64_t mask;
+ uint32_t ctrl;
+ uint32_t pad;
+ } dbg_regs[8];
+};
+
+
+/* Macros to extract fields from the hardware debug information word. */
+#define LOONGARCH_DEBUG_NUM_SLOTS(x) ((x) & 0xffff)
+
+/* 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 loongarch_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.
+
+ In the per-thread arch-specific data area, we define two such
+ variables for per-thread hardware breakpoint and watchpoint
+ settings respectively.
+
+ 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. */
+
+typedef ULONGEST dr_changed_t;
+
+/* Set each of the lower M bits of X to 1; assert X is wide enough. */
+
+#define DR_MARK_ALL_CHANGED(x, m) \
+ do \
+ { \
+ gdb_assert (sizeof ((x)) * 8 >= (m)); \
+ (x) = (((dr_changed_t)1 << (m)) - 1); \
+ } while (0)
+
+#define DR_MARK_N_CHANGED(x, n) \
+ do \
+ { \
+ (x) |= ((dr_changed_t)1 << (n)); \
+ } while (0)
+
+#define DR_CLEAR_CHANGED(x) \
+ do \
+ { \
+ (x) = 0; \
+ } while (0)
+
+#define DR_HAS_CHANGED(x) ((x) != 0)
+#define DR_N_HAS_CHANGED(x, n) ((x) & ((dr_changed_t)1 << (n)))
+
+/* Per-thread arch-specific data we want to keep. */
+
+struct arch_lwp_info
+{
+ /* When bit N is 1, it indicates the Nth hardware breakpoint or
+ watchpoint register pair needs to be updated when the thread is
+ resumed; see loongarch_linux_prepare_to_resume. */
+ dr_changed_t dr_changed_wp;
+};
+
+/* Call ptrace to set the thread TID's hardware breakpoint/watchpoint
+ registers with data from *STATE. */
+
+void loongarch_linux_set_debug_regs (struct loongarch_debug_reg_state *state,
+ int tid, int watchpoint);
+
+/* Get the hardware debug register capacity information from the
+ process represented by TID. */
+
+void loongarch_linux_get_debug_reg_capacity (int tid);
+
+/* Return the debug register state for process PID. If no existing
+ state is found for this process, return nullptr. */
+
+struct loongarch_debug_reg_state *loongarch_lookup_debug_reg_state (pid_t pid);
+
+/* Return the debug register state for process PID. If no existing
+ state is found for this process, create new state. */
+
+struct loongarch_debug_reg_state *loongarch_get_debug_reg_state (pid_t pid);
+
+/* Remove any existing per-process debug state for process PID. */
+
+void loongarch_remove_debug_reg_state (pid_t pid);
+
+
+#endif /* NAT_LOONGARCH_LINUX_HW_POINT_H */
diff --git a/gdb/nat/loongarch-linux.c b/gdb/nat/loongarch-linux.c
new file mode 100644
index 0000000..088d0fc
--- /dev/null
+++ b/gdb/nat/loongarch-linux.c
@@ -0,0 +1,87 @@
+/* Native-dependent code for GNU/Linux on LoongArch processors.
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Contributed by Loongson Ltd.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "gdbsupport/common-defs.h"
+#include "gdbsupport/break-common.h"
+#include "nat/linux-nat.h"
+#include "nat/loongarch-linux-hw-point.h"
+#include "nat/loongarch-linux.h"
+
+#include "elf/common.h"
+#include "nat/gdb_ptrace.h"
+#include <asm/ptrace.h>
+#include <sys/uio.h>
+
+/* Called when resuming a thread LWP.
+ The hardware debug registers are updated when there is any change. */
+
+void
+loongarch_linux_prepare_to_resume (struct lwp_info *lwp)
+{
+ struct 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 == NULL)
+ return;
+
+ if (DR_HAS_CHANGED (info->dr_changed_wp))
+ {
+ ptid_t ptid = ptid_of_lwp (lwp);
+ int tid = ptid.lwp ();
+ struct loongarch_debug_reg_state *state
+ = loongarch_get_debug_reg_state (ptid.pid ());
+
+ if (show_debug_regs)
+ debug_printf ("prepare_to_resume thread %d\n", tid);
+
+ loongarch_linux_set_debug_regs (state, tid, 1);
+ DR_CLEAR_CHANGED (info->dr_changed_wp);
+
+ }
+}
+
+/* Function to call when a new thread is detected. */
+
+void
+loongarch_linux_new_thread (struct lwp_info *lwp)
+{
+ ptid_t ptid = ptid_of_lwp (lwp);
+ struct loongarch_debug_reg_state *state
+ = loongarch_get_debug_reg_state (ptid.pid ());
+ struct arch_lwp_info *info = XCNEW (struct 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 arch_process_info.debug_reg_state). */
+ if (loongarch_any_set_debug_regs_state (state, true))
+ DR_MARK_ALL_CHANGED (info->dr_changed_wp, loongarch_num_wp_regs);
+
+ lwp_set_arch_private_info (lwp, info);
+}
+
+/* See nat/loongarch-linux.h. */
+
+void
+loongarch_linux_delete_thread (struct arch_lwp_info *arch_lwp)
+{
+ xfree (arch_lwp);
+}
diff --git a/gdb/nat/loongarch-linux.h b/gdb/nat/loongarch-linux.h
new file mode 100644
index 0000000..f4bb75a
--- /dev/null
+++ b/gdb/nat/loongarch-linux.h
@@ -0,0 +1,42 @@
+/* Native-dependent code for GNU/Linux on LoongArch processors.
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Contributed by Loongson Ltd.
+
+ 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_LOONGARCH_LINUX_H
+#define NAT_LOONGARCH_LINUX_H
+
+#include <signal.h>
+
+/* Defines ps_err_e, struct ps_prochandle. */
+#include "gdb_proc_service.h"
+
+/* Called when resuming a thread LWP.
+ The hardware debug registers are updated when there is any change. */
+
+void loongarch_linux_prepare_to_resume (struct lwp_info *lwp);
+
+/* Function to call when a new thread is detected. */
+
+void loongarch_linux_new_thread (struct lwp_info *lwp);
+
+/* Function to call when a thread is being deleted. */
+
+void loongarch_linux_delete_thread (struct arch_lwp_info *arch_lwp);
+
+#endif /* LOONGARCH_LINUX_H */