diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 84bc54b303e..f5864607e39 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1601,6 +1601,9 @@ HFILES_NO_SRCDIR = \
 	nat/linux-personality.h \
 	nat/linux-ptrace.h \
 	nat/linux-waitpid.h \
+	nat/loongarch-hw-point.h \
+	nat/loongarch-linux.h \
+	nat/loongarch-linux-hw-point.h \
 	nat/mips-linux-watch.h \
 	nat/ppc-linux.h \
 	nat/x86-cpuid.h \
diff --git a/gdb/configure.nat b/gdb/configure.nat
index 8b98511cef7..1bc070ef8b4 100644
--- a/gdb/configure.nat
+++ b/gdb/configure.nat
@@ -264,7 +264,9 @@ case ${gdb_host} in
 		;;
 	    loongarch)
 		# Host: LoongArch, running GNU/Linux.
-		NATDEPFILES="${NATDEPFILES} loongarch-linux-nat.o linux-nat-trad.o"
+		NATDEPFILES="${NATDEPFILES} loongarch-linux-nat.o linux-nat-trad.o \
+		nat/loongarch-hw-point.o nat/loongarch-linux.o \
+		nat/loongarch-linux-hw-point.o"
 		;;
 	    m32r)
 		# Host: M32R based machine running GNU/Linux
diff --git a/gdb/loongarch-linux-nat.c b/gdb/loongarch-linux-nat.c
index 15fca6ad781..9f5760066b3 100644
--- a/gdb/loongarch-linux-nat.c
+++ b/gdb/loongarch-linux-nat.c
@@ -24,6 +24,9 @@
 #include "linux-nat-trad.h"
 #include "loongarch-tdep.h"
 #include "nat/gdb_ptrace.h"
+#include "nat/loongarch-hw-point.h"
+#include "nat/loongarch-linux.h"
+#include "nat/loongarch-linux-hw-point.h"
 #include "target-descriptions.h"
 
 #include <asm/ptrace.h>
@@ -37,6 +40,37 @@ class loongarch_linux_nat_target final : public linux_nat_trad_target
   void fetch_registers (struct regcache *, int) override;
   void store_registers (struct regcache *, int) override;
 
+  int can_use_hw_breakpoint (enum bptype type, int cnt, int othertype) override;
+  int region_ok_for_hw_watchpoint (CORE_ADDR addr, int len) override;
+
+  int insert_watchpoint (CORE_ADDR addr, int len, enum target_hw_bp_type type,
+			 struct expression *cond) override;
+  int remove_watchpoint (CORE_ADDR addr, int len, enum target_hw_bp_type type,
+			 struct expression *cond) override;
+  bool watchpoint_addr_within_range (CORE_ADDR addr, CORE_ADDR start,
+				     int length) override;
+
+  /* Add our hardware breakpoint and watchpoint implementation.  */
+  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;
+
+  /* These three defer to common nat/ code.  */
+  void low_new_thread (struct lwp_info *lp) override
+  { loongarch_linux_new_thread (lp); }
+  void low_delete_thread (struct arch_lwp_info *lp) override
+  { loongarch_linux_delete_thread (lp); }
+  void low_prepare_to_resume (struct lwp_info *lp) override
+  { loongarch_linux_prepare_to_resume (lp); }
+
+  void low_new_fork (struct lwp_info *parent, pid_t child_pid) override;
+  void low_forget_process (pid_t pid) override;
+
 protected:
   /* Override linux_nat_trad_target methods.  */
   CORE_ADDR register_u_offset (struct gdbarch *gdbarch, int regnum,
@@ -406,6 +440,251 @@ fill_fpregset (const struct regcache *regcache, gdb_fpregset_t *fpregset,
 				     sizeof (gdb_fpregset_t));
 }
 
+/* Helper for the "stopped_data_address" target method.  Returns TRUE
+   if a hardware watchpoint trap at ADDR_TRAP matches a set watchpoint.
+   The address of the matched watchpoint is returned in *ADDR_P.  */
+
+static bool
+loongarch_stopped_data_address (const struct loongarch_debug_reg_state *state,
+			      CORE_ADDR addr_trap, CORE_ADDR *addr_p)
+{
+
+  int i;
+
+  for (i = loongarch_num_wp_regs - 1; i >= 0; --i)
+    {
+      const CORE_ADDR addr_watch = state->dr_addr_wp[i];
+
+      if (state->dr_ref_count_wp[i]
+	  && DR_CONTROL_ENABLED (state->dr_ctrl_wp[i])
+	  && addr_trap == addr_watch)
+	{
+	  *addr_p = addr_watch;
+	  return true;
+	}
+    }
+  return false;
+}
+
+
+/* Returns the number of hardware watchpoints of type TYPE that we can
+   set.  Value is positive if we can set CNT watchpoints, zero if
+   setting watchpoints of type TYPE is not supported, and negative if
+   CNT is more than the maximum number of watchpoints of type TYPE
+   that we can support.  TYPE is one of bp_hardware_watchpoint,
+   bp_read_watchpoint, bp_write_watchpoint, or bp_hardware_breakpoint.
+   CNT is the number of such watchpoints used so far (including this
+   one).  OTHERTYPE is non-zero if other types of watchpoints are
+   currently enabled.  */
+
+int
+loongarch_linux_nat_target::can_use_hw_breakpoint (enum bptype type, int cnt,
+						   int othertype)
+{
+  if (type == bp_hardware_watchpoint || type == bp_read_watchpoint
+      || type == bp_access_watchpoint || type == bp_watchpoint)
+    {
+      if (loongarch_num_wp_regs == 0)
+	return 0;
+    }
+  else if (type == bp_hardware_breakpoint)
+    {
+      return 0;
+    }
+  else
+    gdb_assert_not_reached ("unexpected breakpoint type");
+
+  /* We always return 1 here because we don't have enough information
+     about possible overlap of addresses that they want to watch.  As an
+     extreme example, consider the case where all the watchpoints watch
+     the same address and the same region length: then we can handle a
+     virtually unlimited number of watchpoints, due to debug register
+     sharing implemented via reference counts.  */
+  return 1;
+
+}
+
+int
+loongarch_linux_nat_target::region_ok_for_hw_watchpoint (CORE_ADDR addr,
+							int len)
+{
+  return loongarch_region_ok_for_watchpoint (addr, len);
+}
+
+/* Insert a watchpoint to watch a memory region which starts at
+   address ADDR and whose length is LEN bytes.  Watch memory accesses
+   of the type TYPE.  Return 0 on success, -1 on failure.  */
+
+int
+loongarch_linux_nat_target::insert_watchpoint (CORE_ADDR addr, int len,
+					      enum target_hw_bp_type type,
+					      struct expression *cond)
+{
+  int ret;
+  struct loongarch_debug_reg_state *state
+	= loongarch_get_debug_reg_state (inferior_ptid.pid ());
+
+  if (show_debug_regs)
+    gdb_printf (gdb_stdlog,
+		"insert_watchpoint on entry (addr=0x%08lx, len=%d)\n",
+		(unsigned long) addr, len);
+
+  gdb_assert (type != hw_execute);
+
+  ret = loongarch_handle_watchpoint (type, addr, len, 1 /* is_insert */,
+				     inferior_ptid, state);
+
+  if (show_debug_regs)
+    {
+      loongarch_show_debug_reg_state (state,
+				      "insert_watchpoint", addr, len, type);
+    }
+
+  return ret;
+
+}
+
+/* Remove a watchpoint that watched the memory region which starts at
+   address ADDR, whose length is LEN bytes, and for accesses of the
+   type TYPE.  Return 0 on success, -1 on failure.  */
+
+int
+loongarch_linux_nat_target::remove_watchpoint (CORE_ADDR addr, int len,
+					      enum target_hw_bp_type type,
+					      struct expression *cond)
+{
+  int ret;
+  struct loongarch_debug_reg_state *state
+	= loongarch_get_debug_reg_state (inferior_ptid.pid ());
+
+  if (show_debug_regs)
+    gdb_printf (gdb_stdlog,
+		"remove_watchpoint on entry (addr=0x%08lx, len=%d)\n",
+		(unsigned long) addr, len);
+
+  gdb_assert (type != hw_execute);
+
+  ret = loongarch_handle_watchpoint (type, addr, len, 0 /* is_insert */,
+				     inferior_ptid, state);
+
+  if (show_debug_regs)
+    {
+      loongarch_show_debug_reg_state (state,
+				      "remove_watchpoint", addr, len, type);
+    }
+
+  return ret;
+
+}
+
+bool
+loongarch_linux_nat_target::watchpoint_addr_within_range (CORE_ADDR addr,
+							  CORE_ADDR start,
+							  int length)
+{
+  return start <= addr && start + length - 1 >= addr;
+}
+
+
+/* Implement the "stopped_data_address" target_ops method.  */
+
+bool
+loongarch_linux_nat_target::stopped_data_address (CORE_ADDR *addr_p)
+{
+  siginfo_t siginfo;
+  struct loongarch_debug_reg_state *state;
+
+  if (!linux_nat_get_siginfo (inferior_ptid, &siginfo))
+    return false;
+
+  /* This must be a hardware breakpoint.  */
+  if (siginfo.si_signo != SIGTRAP || (siginfo.si_code & 0xffff) != TRAP_HWBKPT)
+    return false;
+
+  /* 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.  */
+  struct gdbarch *gdbarch = thread_architecture (inferior_ptid);
+  const CORE_ADDR addr_trap
+    = gdbarch_remove_non_address_bits (gdbarch, (CORE_ADDR) siginfo.si_addr);
+
+  /* Check if the address matches any watched address.  */
+  state = loongarch_get_debug_reg_state (inferior_ptid.pid ());
+
+  return loongarch_stopped_data_address (state, addr_trap, addr_p);
+}
+
+/* Implement the "stopped_by_watchpoint" target_ops method.  */
+
+bool
+loongarch_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
+loongarch_linux_nat_target::post_startup_inferior (ptid_t ptid)
+{
+  low_forget_process (ptid.pid ());
+  loongarch_linux_get_debug_reg_capacity (ptid.pid ());
+  linux_nat_target::post_startup_inferior (ptid);
+}
+
+/* Implement the "post_attach" target_ops method.  */
+
+void
+loongarch_linux_nat_target::post_attach (int pid)
+{
+  low_forget_process (pid);
+  /* Get the hardware debug register capacity. If
+     loongarch_linux_get_debug_reg_capacity is not called
+     (as it is in loongarch_linux_child_post_startup_inferior) then
+     software watchpoints will be used instead of hardware
+     watchpoints when attaching to a target.  */
+  loongarch_linux_get_debug_reg_capacity (pid);
+  linux_nat_target::post_attach (pid);
+}
+
+/* linux_nat_new_fork hook.   */
+
+void
+loongarch_linux_nat_target::low_new_fork (struct lwp_info *parent,
+					  pid_t child_pid)
+{
+  pid_t parent_pid;
+  struct loongarch_debug_reg_state *parent_state;
+  struct loongarch_debug_reg_state *child_state;
+
+  /* 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;
+
+  /* GDB core assumes the child inherits the watchpoints/hw
+     breakpoints of the parent, and will remove them all from the
+     forked off process.  Copy the debug registers mirrors into the
+     new process so that all breakpoints and watchpoints can be
+     removed together.  */
+
+  parent_pid = parent->ptid.pid ();
+  parent_state = loongarch_get_debug_reg_state (parent_pid);
+  child_state = loongarch_get_debug_reg_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
+loongarch_linux_nat_target::low_forget_process (pid_t pid)
+{
+  loongarch_remove_debug_reg_state (pid);
+}
+
 /* Initialize LoongArch Linux native support.  */
 
 void _initialize_loongarch_linux_nat ();
diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c
index af0d6896143..757f4acc489 100644
--- a/gdb/loongarch-tdep.c
+++ b/gdb/loongarch-tdep.c
@@ -1869,6 +1869,7 @@ loongarch_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   set_gdbarch_software_single_step (gdbarch, loongarch_software_single_step);
   set_gdbarch_breakpoint_kind_from_pc (gdbarch, loongarch_breakpoint::kind_from_pc);
   set_gdbarch_sw_breakpoint_from_kind (gdbarch, loongarch_breakpoint::bp_from_kind);
+  set_gdbarch_have_nonsteppable_watchpoint (gdbarch, 1);
 
   /* Frame unwinders. Use DWARF debug info if available, otherwise use our own unwinder.  */
   set_gdbarch_dwarf2_reg_to_regnum (gdbarch, loongarch_dwarf2_reg_to_regnum);
diff --git a/gdb/nat/loongarch-hw-point.c b/gdb/nat/loongarch-hw-point.c
new file mode 100644
index 00000000000..30c08dc498c
--- /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_PLV_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_PLV_ENABLE) == 0) // PLV0-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_PLV_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 00000000000..69965ab2165
--- /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_PLV_ENABLE		0x1e
+
+#define DR_CONTROL_ENABLED(ctrl)  ((ctrl & CTRL_PLV_ENABLE) == CTRL_PLV_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 00000000000..14ed61f0825
--- /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.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 00000000000..4086907e3c0
--- /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 00000000000..088d0fcbf34
--- /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 00000000000..f4bb75af88c
--- /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 */
diff --git a/include/elf/common.h b/include/elf/common.h
index 7e62c34643c..f993146717c 100644
--- a/include/elf/common.h
+++ b/include/elf/common.h
@@ -720,6 +720,8 @@
 					/*   note name must be "LINUX".  */
 #define NT_LARCH_LBT    0xa04		/* LoongArch Binary Translation registers */
 					/*   note name must be "CORE".  */
+#define NT_LOONGARCH_HW_WATCH   0xa06	/* LoongArch hardware watchpoint registers */
+					/*   note name must be "LINUX".  */
 #define NT_RISCV_CSR    0x900		/* RISC-V Control and Status Registers */
 					/*   note name must be "LINUX".  */
 #define NT_SIGINFO	0x53494749	/* Fields of siginfo_t.  */
