diff --git a/gdb/loongarch-linux-nat.c b/gdb/loongarch-linux-nat.c
index 9f5760066b3..873628775e2 100644
--- a/gdb/loongarch-linux-nat.c
+++ b/gdb/loongarch-linux-nat.c
@@ -54,6 +54,11 @@ class loongarch_linux_nat_target final : public linux_nat_trad_target
   bool stopped_by_watchpoint () override;
   bool stopped_data_address (CORE_ADDR *) override;
 
+  int insert_hw_breakpoint (struct gdbarch *gdbarch,
+			    struct bp_target_info *bp_tgt) override;
+  int remove_hw_breakpoint (struct gdbarch *gdbarch,
+			    struct bp_target_info *bp_tgt) override;
+
   /* Override the GNU/Linux inferior startup hook.  */
   void post_startup_inferior (ptid_t) override;
 
@@ -489,7 +494,8 @@ loongarch_linux_nat_target::can_use_hw_breakpoint (enum bptype type, int cnt,
     }
   else if (type == bp_hardware_breakpoint)
     {
-      return 0;
+      if (loongarch_num_bp_regs == 0)
+	return 0;
     }
   else
     gdb_assert_not_reached ("unexpected breakpoint type");
@@ -624,6 +630,72 @@ loongarch_linux_nat_target::stopped_by_watchpoint ()
   return stopped_data_address (&addr);
 }
 
+/* Insert a hardware-assisted breakpoint at BP_TGT->reqstd_address.
+   Return 0 on success, -1 on failure.  */
+
+int
+loongarch_linux_nat_target::insert_hw_breakpoint (struct gdbarch *gdbarch,
+						  struct bp_target_info *bp_tgt)
+{
+  int ret;
+  CORE_ADDR addr = bp_tgt->placed_address = bp_tgt->reqstd_address;
+  int len;
+  const enum target_hw_bp_type type = hw_execute;
+  struct loongarch_debug_reg_state *state
+	= loongarch_get_debug_reg_state (inferior_ptid.pid ());
+
+  gdbarch_breakpoint_from_pc (gdbarch, &addr, &len);
+
+  if (show_debug_regs)
+    gdb_printf (gdb_stdlog,
+		"insert_hw_breakpoint on entry (addr=0x%08lx, len=%d))\n",
+		(unsigned long) addr, len);
+
+  ret = loongarch_handle_breakpoint (type, addr, len, 1 /* is_insert */,
+				     inferior_ptid, state);
+
+  if (show_debug_regs)
+    {
+      loongarch_show_debug_reg_state (state,
+				      "insert_hw_breakpoint", addr, len, type);
+    }
+
+  return ret;
+}
+
+/* Remove a hardware-assisted breakpoint at BP_TGT->placed_address.
+   Return 0 on success, -1 on failure.  */
+
+int
+loongarch_linux_nat_target::remove_hw_breakpoint (struct gdbarch *gdbarch,
+						  struct bp_target_info *bp_tgt)
+{
+  int ret;
+  CORE_ADDR addr = bp_tgt->placed_address;
+  int len = 4;
+  const enum target_hw_bp_type type = hw_execute;
+  struct loongarch_debug_reg_state *state
+    = loongarch_get_debug_reg_state (inferior_ptid.pid ());
+
+  gdbarch_breakpoint_from_pc (gdbarch, &addr, &len);
+
+  if (show_debug_regs)
+    gdb_printf (gdb_stdlog,
+		"remove_hw_breakpoint on entry (addr=0x%08lx, len=%d))\n",
+		(unsigned long) addr, len);
+
+  ret = loongarch_handle_breakpoint (type, addr, len, 0 /* is_insert */,
+				     inferior_ptid, state);
+
+  if (show_debug_regs)
+    {
+      loongarch_show_debug_reg_state (state,
+				      "remove_hw_watchpoint", addr, len, type);
+    }
+
+  return ret;
+}
+
 /* Implement the virtual inf_ptrace_target::post_startup_inferior method.  */
 
 void
diff --git a/gdb/nat/loongarch-hw-point.c b/gdb/nat/loongarch-hw-point.c
index 30c08dc498c..38e93e3cead 100644
--- a/gdb/nat/loongarch-hw-point.c
+++ b/gdb/nat/loongarch-hw-point.c
@@ -27,6 +27,7 @@
 /* Number of hardware breakpoints/watchpoints the target supports.
    They are initialized with values obtained via ptrace.  */
 
+int loongarch_num_bp_regs;
 int loongarch_num_wp_regs;
 
 /* Given the hardware breakpoint or watchpoint type TYPE and its
@@ -112,7 +113,10 @@ loongarch_dr_state_insert_one_point (ptid_t ptid,
     }
   else
     {
-      return -1;
+      num_regs = loongarch_num_bp_regs;
+      dr_addr_p = state->dr_addr_bp;
+      dr_ctrl_p = state->dr_ctrl_bp;
+      dr_ref_count = state->dr_ref_count_bp;
     }
 
   ctrl = loongarch_point_encode_ctrl_reg (type, len);
@@ -184,7 +188,10 @@ loongarch_dr_state_remove_one_point (ptid_t ptid,
     }
   else
     {
-      return -1;
+      num_regs = loongarch_num_bp_regs;
+      dr_addr_p = state->dr_addr_bp;
+      dr_ctrl_p = state->dr_ctrl_bp;
+      dr_ref_count = state->dr_ref_count_bp;
     }
 
   ctrl = loongarch_point_encode_ctrl_reg (type, len);
@@ -214,6 +221,20 @@ loongarch_dr_state_remove_one_point (ptid_t ptid,
   return 0;
 }
 
+int
+loongarch_handle_breakpoint (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, -1);
+  else
+    return loongarch_dr_state_remove_one_point (ptid, state, type, addr,
+						len, -1);
+}
+
+
 int
 loongarch_handle_watchpoint (enum target_hw_bp_type type, CORE_ADDR addr,
 			     int len, int is_insert, ptid_t ptid,
@@ -234,12 +255,12 @@ bool
 loongarch_any_set_debug_regs_state (loongarch_debug_reg_state *state,
 				    bool watchpoint)
 {
-  int count = watchpoint ? loongarch_num_wp_regs : 0;
+  int count = watchpoint ? loongarch_num_wp_regs : loongarch_num_bp_regs;
   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;
+  const CORE_ADDR *addr = watchpoint ? state->dr_addr_wp : state->dr_addr_bp;
+  const unsigned int *ctrl = watchpoint ? state->dr_ctrl_wp : state->dr_ctrl_bp;
 
   for (int i = 0; i < count; i++)
     if (addr[i] != 0 || ctrl[i] != 0)
@@ -268,6 +289,12 @@ loongarch_show_debug_reg_state (struct loongarch_debug_reg_state *state,
 			   : "??unknown??"))));
   debug_printf (":\n");
 
+  debug_printf ("\tBREAKPOINTs:\n");
+  for (i = 0; i < loongarch_num_bp_regs; i++)
+    debug_printf ("\tBP%d: addr=%s, ctrl=0x%08x, ref.count=%d\n",
+		  i, core_addr_to_string_nz (state->dr_addr_bp[i]),
+		  state->dr_ctrl_bp[i], state->dr_ref_count_bp[i]);
+
   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",
diff --git a/gdb/nat/loongarch-hw-point.h b/gdb/nat/loongarch-hw-point.h
index 69965ab2165..a05098b3ee1 100644
--- a/gdb/nat/loongarch-hw-point.h
+++ b/gdb/nat/loongarch-hw-point.h
@@ -33,6 +33,7 @@
    Neither of these values may exceed the width of dr_changed_t
    measured in bits.  */
 
+#define LOONGARCH_HBP_MAX_NUM 8
 #define LOONGARCH_HWP_MAX_NUM 8
 
 
@@ -53,12 +54,18 @@
 
 struct loongarch_debug_reg_state
 {
+  /* hardware breakpoint */
+  CORE_ADDR dr_addr_bp[LOONGARCH_HBP_MAX_NUM];
+  unsigned int dr_ctrl_bp[LOONGARCH_HBP_MAX_NUM];
+  unsigned int dr_ref_count_bp[LOONGARCH_HBP_MAX_NUM];
+
   /* 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_bp_regs;
 extern int loongarch_num_wp_regs;
 
 /* Invoked when IDXth breakpoint/watchpoint register pair needs to be
@@ -68,6 +75,10 @@ void loongarch_notify_debug_reg_change (ptid_t ptid, int is_watchpoint,
 				      unsigned int idx);
 
 
+int loongarch_handle_breakpoint (enum target_hw_bp_type type, CORE_ADDR addr,
+				 int len, int is_insert, ptid_t ptid,
+				 struct loongarch_debug_reg_state *state);
+
 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);
diff --git a/gdb/nat/loongarch-linux-hw-point.c b/gdb/nat/loongarch-linux-hw-point.c
index 14ed61f0825..a8aa9e6cd77 100644
--- a/gdb/nat/loongarch-linux-hw-point.c
+++ b/gdb/nat/loongarch-linux-hw-point.c
@@ -62,9 +62,6 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint,
   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);
@@ -74,14 +71,19 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint,
   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));
+      debug_printf ("\ttid%d, dr_changed_bp=0x%s, "
+		    "dr_changed_wp=0x%s\n", tid,
+		    phex (info->dr_changed_bp, 8),
+		    phex (info->dr_changed_wp, 8));
     }
 
-  dr_changed_ptr = &info->dr_changed_wp;
+  dr_changed_ptr = is_watchpoint ? &info->dr_changed_wp
+		   : &info->dr_changed_bp;
   dr_changed = *dr_changed_ptr;
 
-  gdb_assert (idx >= 0 && idx <= loongarch_num_wp_regs);
+  gdb_assert (idx >= 0
+	      && (idx <= (is_watchpoint ? loongarch_num_wp_regs
+			  : loongarch_num_bp_regs)));
 
   /* The actual update is done later just before resuming the lwp,
      we just mark that one register pair needs updating.  */
@@ -95,8 +97,10 @@ loongarch_dr_change_callback (struct lwp_info *lwp, int is_watchpoint,
 
   if (show_debug_regs)
     {
-      debug_printf ("\tOn exit:\n\ttid%d, dr_changed_wp=0x%s\n",
-		    tid, phex (info->dr_changed_wp, 8));
+      debug_printf ("\tOn exit:\n\ttid%d, dr_changed_bp=0x%s, "
+		    "dr_changed_wp=0x%s\n", tid,
+		    phex (info->dr_changed_bp, 8),
+		    phex (info->dr_changed_wp, 8));
     }
 
   return 0;
@@ -136,9 +140,9 @@ loongarch_linux_set_debug_regs (struct loongarch_debug_reg_state *state,
 
   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;
+  count = watchpoint ? loongarch_num_wp_regs : loongarch_num_bp_regs;
+  addr = watchpoint ? state->dr_addr_wp : state->dr_addr_bp;
+  ctrl = watchpoint ? state->dr_ctrl_wp : state->dr_ctrl_bp;
 
   if (count == 0)
     return;
@@ -151,7 +155,9 @@ loongarch_linux_set_debug_regs (struct loongarch_debug_reg_state *state,
       regs.dbg_regs[i].ctrl = ctrl[i];
     }
 
-  if (ptrace(PTRACE_SETREGSET, tid, NT_LOONGARCH_HW_WATCH, (void *) &iov))
+  if (ptrace(PTRACE_SETREGSET, tid,
+	     watchpoint ? NT_LOONGARCH_HW_WATCH : NT_LOONGARCH_HW_BREAK,
+	     (void *) &iov))
     {
       if (errno == EINVAL)
 	error (_("Invalid argument setting hardware debug registers"));
@@ -194,6 +200,25 @@ loongarch_linux_get_debug_reg_capacity (int tid)
       loongarch_num_wp_regs = 0;
     }
 
+  /* Get hardware breakpoint register info.  */
+  result = ptrace (PTRACE_GETREGSET, tid, NT_LOONGARCH_HW_BREAK, &iov);
+  if ( result == 0)
+    {
+      loongarch_num_bp_regs = LOONGARCH_DEBUG_NUM_SLOTS (dreg_state.dbg_info);
+      if (loongarch_num_bp_regs > LOONGARCH_HBP_MAX_NUM)
+	{
+	  warning (_("Unexpected number of hardware breakpoint registers"
+		     " reported by ptrace, got %d, expected %d."),
+		   loongarch_num_bp_regs, LOONGARCH_HBP_MAX_NUM);
+	  loongarch_num_bp_regs = LOONGARCH_HBP_MAX_NUM;
+	}
+    }
+  else
+    {
+      warning (_("Unable to determine the number of hardware breakpoints"
+		 " available."));
+      loongarch_num_bp_regs = 0;
+    }
 }
 
 /* Return the debug register state for process PID.  If no existing
diff --git a/gdb/nat/loongarch-linux-hw-point.h b/gdb/nat/loongarch-linux-hw-point.h
index 4086907e3c0..8d96443f5e0 100644
--- a/gdb/nat/loongarch-linux-hw-point.h
+++ b/gdb/nat/loongarch-linux-hw-point.h
@@ -93,6 +93,7 @@ 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_bp;
   dr_changed_t dr_changed_wp;
 };
 
diff --git a/gdb/nat/loongarch-linux.c b/gdb/nat/loongarch-linux.c
index 088d0fcbf34..03a0aaf9fd9 100644
--- a/gdb/nat/loongarch-linux.c
+++ b/gdb/nat/loongarch-linux.c
@@ -43,7 +43,8 @@ loongarch_linux_prepare_to_resume (struct lwp_info *lwp)
   if (info == NULL)
     return;
 
-  if (DR_HAS_CHANGED (info->dr_changed_wp))
+  if (DR_HAS_CHANGED (info->dr_changed_bp)
+      || DR_HAS_CHANGED (info->dr_changed_wp))
     {
       ptid_t ptid = ptid_of_lwp (lwp);
       int tid = ptid.lwp ();
@@ -53,9 +54,19 @@ loongarch_linux_prepare_to_resume (struct lwp_info *lwp)
       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);
-
+      /* Watchpoints.  */
+      if (DR_HAS_CHANGED (info->dr_changed_wp))
+	{
+	  loongarch_linux_set_debug_regs (state, tid, 1);
+	  DR_CLEAR_CHANGED (info->dr_changed_wp);
+	}
+
+      /* Breakpoints.  */
+      if (DR_HAS_CHANGED (info->dr_changed_bp))
+	{
+	  loongarch_linux_set_debug_regs (state, tid, 0);
+	  DR_CLEAR_CHANGED (info->dr_changed_bp);
+	}
     }
 }
 
@@ -72,6 +83,8 @@ loongarch_linux_new_thread (struct lwp_info *lwp)
   /* 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, false))
+    DR_MARK_ALL_CHANGED (info->dr_changed_bp, loongarch_num_bp_regs);
   if (loongarch_any_set_debug_regs_state (state, true))
     DR_MARK_ALL_CHANGED (info->dr_changed_wp, loongarch_num_wp_regs);
 
diff --git a/include/elf/common.h b/include/elf/common.h
index f993146717c..91715490be3 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_BREAK   0xa05	/* LoongArch hardware breakpoint registers */
+					/*   note name must be "LINUX".  */
 #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 */
