| /* Native-dependent code for GNU/Linux on LoongArch processors. | 
 |  | 
 |    Copyright (C) 2022-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 "cli/cli-cmds.h" | 
 | #include "elf/common.h" | 
 | #include "gregset.h" | 
 | #include "inferior.h" | 
 | #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> | 
 |  | 
 | /* 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 nat/loongarch-linux-hw-point.h.  */ | 
 |  | 
 | 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.  */ | 
 |  | 
 | static void | 
 | loongarch_remove_debug_reg_state (pid_t pid) | 
 | { | 
 |   loongarch_debug_process_state.erase (pid); | 
 | } | 
 |  | 
 | /* LoongArch Linux native additions to the default Linux support.  */ | 
 |  | 
 | class loongarch_linux_nat_target final : public linux_nat_trad_target | 
 | { | 
 | public: | 
 |   /* Add our register access methods.  */ | 
 |   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; | 
 |  | 
 |   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; | 
 |  | 
 |   /* 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, | 
 | 			       int store_p) override; | 
 | }; | 
 |  | 
 | /* Fill GDB's register array with the general-purpose, orig_a0, pc and badv | 
 |    register values from the current thread.  */ | 
 |  | 
 | static void | 
 | fetch_gregs_from_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   elf_gregset_t regset; | 
 |  | 
 |   if (regnum == -1 || (regnum >= 0 && regnum < 32) | 
 |       || regnum == LOONGARCH_ORIG_A0_REGNUM | 
 |       || regnum == LOONGARCH_PC_REGNUM | 
 |       || regnum == LOONGARCH_BADV_REGNUM) | 
 |   { | 
 |     struct iovec iov; | 
 |  | 
 |     iov.iov_base = ®set; | 
 |     iov.iov_len = sizeof (regset); | 
 |  | 
 |     if (ptrace (PTRACE_GETREGSET, tid, NT_PRSTATUS, (long) &iov) < 0) | 
 |       perror_with_name (_("Couldn't get NT_PRSTATUS registers")); | 
 |     else | 
 |       loongarch_gregset.supply_regset (nullptr, regcache, -1, | 
 | 				       ®set, sizeof (regset)); | 
 |   } | 
 | } | 
 |  | 
 | /* Store to the current thread the valid general-purpose, orig_a0, pc and badv | 
 |    register values in the GDB's register array.  */ | 
 |  | 
 | static void | 
 | store_gregs_to_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   elf_gregset_t regset; | 
 |  | 
 |   if (regnum == -1 || (regnum >= 0 && regnum < 32) | 
 |       || regnum == LOONGARCH_ORIG_A0_REGNUM | 
 |       || regnum == LOONGARCH_PC_REGNUM | 
 |       || regnum == LOONGARCH_BADV_REGNUM) | 
 |   { | 
 |     struct iovec iov; | 
 |  | 
 |     iov.iov_base = ®set; | 
 |     iov.iov_len = sizeof (regset); | 
 |  | 
 |     if (ptrace (PTRACE_GETREGSET, tid, NT_PRSTATUS, (long) &iov) < 0) | 
 |       perror_with_name (_("Couldn't get NT_PRSTATUS registers")); | 
 |     else | 
 |       { | 
 | 	loongarch_gregset.collect_regset (nullptr, regcache, regnum, | 
 | 					  ®set, sizeof (regset)); | 
 | 	if (ptrace (PTRACE_SETREGSET, tid, NT_PRSTATUS, (long) &iov) < 0) | 
 | 	  perror_with_name (_("Couldn't set NT_PRSTATUS registers")); | 
 |       } | 
 |   } | 
 | } | 
 |  | 
 | /* Fill GDB's register array with the fp, fcc and fcsr | 
 |    register values from the current thread.  */ | 
 |  | 
 | static void | 
 | fetch_fpregs_from_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   elf_fpregset_t regset; | 
 |  | 
 |   if ((regnum == -1) | 
 |       || (regnum >= LOONGARCH_FIRST_FP_REGNUM && regnum <= LOONGARCH_FCSR_REGNUM)) | 
 |     { | 
 |       struct iovec iovec = { .iov_base = ®set, .iov_len = sizeof (regset) }; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGSET, tid, NT_FPREGSET, (long) &iovec) < 0) | 
 | 	perror_with_name (_("Couldn't get NT_FPREGSET registers")); | 
 |       else | 
 | 	loongarch_fpregset.supply_regset (nullptr, regcache, -1, | 
 | 					  ®set, sizeof (regset)); | 
 |     } | 
 | } | 
 |  | 
 | /* Store to the current thread the valid fp, fcc and fcsr | 
 |    register values in the GDB's register array.  */ | 
 |  | 
 | static void | 
 | store_fpregs_to_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   elf_fpregset_t regset; | 
 |  | 
 |   if ((regnum == -1) | 
 |       || (regnum >= LOONGARCH_FIRST_FP_REGNUM && regnum <= LOONGARCH_FCSR_REGNUM)) | 
 |     { | 
 |       struct iovec iovec = { .iov_base = ®set, .iov_len = sizeof (regset) }; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGSET, tid, NT_FPREGSET, (long) &iovec) < 0) | 
 | 	perror_with_name (_("Couldn't get NT_FPREGSET registers")); | 
 |       else | 
 | 	{ | 
 | 	  loongarch_fpregset.collect_regset (nullptr, regcache, regnum, | 
 | 					     ®set, sizeof (regset)); | 
 | 	  if (ptrace (PTRACE_SETREGSET, tid, NT_FPREGSET, (long) &iovec) < 0) | 
 | 	    perror_with_name (_("Couldn't set NT_FPREGSET registers")); | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 | /* Fill GDB's register array with the Loongson SIMD Extension | 
 |    register values from the current thread.  */ | 
 |  | 
 | static void | 
 | fetch_lsxregs_from_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   elf_lsxregset_t regset; | 
 |  | 
 |   if ((regnum == -1) | 
 |       || (regnum >= LOONGARCH_FIRST_LSX_REGNUM && regnum < LOONGARCH_FIRST_LASX_REGNUM)) | 
 |     { | 
 |       struct iovec iovec = { .iov_base = ®set, .iov_len = sizeof (regset) }; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGSET, tid, NT_LARCH_LSX, (long) &iovec) < 0) | 
 | 	{ | 
 | 	  /* If kernel dose not support lsx, just return. */ | 
 | 	  if (errno == EINVAL) | 
 | 	    return; | 
 |  | 
 | 	  perror_with_name (_("Couldn't get NT_LARCH_LSX registers")); | 
 | 	} | 
 |       else | 
 | 	loongarch_lsxregset.supply_regset (nullptr, regcache, -1, | 
 | 					   ®set, sizeof (regset)); | 
 |     } | 
 | } | 
 |  | 
 | /* Store to the current thread the valid Loongson SIMD Extension | 
 |    register values in the GDB's register array.  */ | 
 |  | 
 | static void | 
 | store_lsxregs_to_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   elf_lsxregset_t regset; | 
 |  | 
 |   if ((regnum == -1) | 
 |       || (regnum >= LOONGARCH_FIRST_LSX_REGNUM && regnum < LOONGARCH_FIRST_LASX_REGNUM)) | 
 |     { | 
 |       struct iovec iovec = { .iov_base = ®set, .iov_len = sizeof (regset) }; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGSET, tid, NT_LARCH_LSX, (long) &iovec) < 0) | 
 | 	{ | 
 | 	  /* If kernel dose not support lsx, just return. */ | 
 | 	  if (errno == EINVAL) | 
 | 	    return; | 
 |  | 
 | 	  perror_with_name (_("Couldn't get NT_LARCH_LSX registers")); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  loongarch_lsxregset.collect_regset (nullptr, regcache, regnum, | 
 | 					     ®set, sizeof (regset)); | 
 | 	  if (ptrace (PTRACE_SETREGSET, tid, NT_LARCH_LSX, (long) &iovec) < 0) | 
 | 	    perror_with_name (_("Couldn't set NT_LARCH_LSX registers")); | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 | /* Fill GDB's register array with the Loongson Advanced SIMD Extension | 
 |    register values from the current thread.  */ | 
 |  | 
 | static void | 
 | fetch_lasxregs_from_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   elf_lasxregset_t regset; | 
 |  | 
 |   if ((regnum == -1) | 
 |       || (regnum >= LOONGARCH_FIRST_LASX_REGNUM | 
 | 	  && regnum < LOONGARCH_FIRST_LASX_REGNUM + LOONGARCH_LINUX_NUM_LASXREGSET)) | 
 |     { | 
 |       struct iovec iovec = { .iov_base = ®set, .iov_len = sizeof (regset) }; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGSET, tid, NT_LARCH_LASX, (long) &iovec) < 0) | 
 | 	{ | 
 | 	  /* If kernel dose not support lasx, just return. */ | 
 | 	  if (errno == EINVAL) | 
 | 	    return; | 
 |  | 
 | 	  perror_with_name (_("Couldn't get NT_LARCH_LSX registers")); | 
 | 	} | 
 |       else | 
 | 	loongarch_lasxregset.supply_regset (nullptr, regcache, -1, | 
 | 					    ®set, sizeof (regset)); | 
 |     } | 
 | } | 
 |  | 
 | /* Store to the current thread the valid Loongson Advanced SIMD Extension | 
 |    register values in the GDB's register array.  */ | 
 |  | 
 | static void | 
 | store_lasxregs_to_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   elf_lasxregset_t regset; | 
 |  | 
 |   if ((regnum == -1) | 
 |       || (regnum >= LOONGARCH_FIRST_LASX_REGNUM | 
 | 	  && regnum < LOONGARCH_FIRST_LASX_REGNUM + LOONGARCH_LINUX_NUM_LASXREGSET)) | 
 |     { | 
 |       struct iovec iovec = { .iov_base = ®set, .iov_len = sizeof (regset) }; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGSET, tid, NT_LARCH_LASX, (long) &iovec) < 0) | 
 | 	{ | 
 | 	  /* If kernel dose not support lasx, just return. */ | 
 | 	  if (errno == EINVAL) | 
 | 	    return; | 
 |  | 
 | 	  perror_with_name (_("Couldn't get NT_LARCH_LSX registers")); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  loongarch_lasxregset.collect_regset (nullptr, regcache, regnum, | 
 | 					      ®set, sizeof (regset)); | 
 | 	  if (ptrace (PTRACE_SETREGSET, tid, NT_LARCH_LASX, (long) &iovec) < 0) | 
 | 	    perror_with_name (_("Couldn't set NT_LARCH_LASX registers")); | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | /* Fill GDB's register array with the lbt register values | 
 |    from the current thread.  */ | 
 |  | 
 | static void | 
 | fetch_lbt_from_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   gdb_byte regset[LOONGARCH_LBT_REGS_SIZE]; | 
 |  | 
 |   if (regnum == -1 | 
 |       || (regnum >= LOONGARCH_FIRST_SCR_REGNUM | 
 | 	  && regnum <= LOONGARCH_FTOP_REGNUM)) | 
 |     { | 
 |       struct iovec iov; | 
 |  | 
 |       iov.iov_base = regset; | 
 |       iov.iov_len = LOONGARCH_LBT_REGS_SIZE; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGSET, tid, NT_LARCH_LBT, (long) &iov) < 0) | 
 | 	{ | 
 | 	  /* If kernel dose not support lbt, just return. */ | 
 | 	  if (errno == EINVAL) | 
 | 	    return; | 
 | 	  perror_with_name (_("Couldn't get NT_LARCH_LBT registers")); | 
 | 	} | 
 |       else | 
 | 	loongarch_lbtregset.supply_regset (nullptr, regcache, -1, | 
 | 					   regset, LOONGARCH_LBT_REGS_SIZE); | 
 |     } | 
 | } | 
 |  | 
 | /* Store to the current thread the valid lbt register values | 
 |    in the GDB's register array.  */ | 
 |  | 
 | static void | 
 | store_lbt_to_thread (struct regcache *regcache, int regnum, pid_t tid) | 
 | { | 
 |   gdb_byte regset[LOONGARCH_LBT_REGS_SIZE]; | 
 |  | 
 |   if (regnum == -1 | 
 |       || (regnum >= LOONGARCH_FIRST_SCR_REGNUM | 
 | 	  && regnum <= LOONGARCH_FTOP_REGNUM)) | 
 |     { | 
 |       struct iovec iov; | 
 |  | 
 |       iov.iov_base = regset; | 
 |       iov.iov_len = LOONGARCH_LBT_REGS_SIZE; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGSET, tid, NT_LARCH_LBT, (long) &iov) < 0) | 
 | 	{ | 
 | 	  /* If kernel dose not support lbt, just return. */ | 
 | 	  if (errno == EINVAL) | 
 | 	    return; | 
 | 	  perror_with_name (_("Couldn't get NT_LARCH_LBT registers")); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  loongarch_lbtregset.collect_regset (nullptr, regcache, regnum, | 
 | 					    regset, LOONGARCH_LBT_REGS_SIZE); | 
 | 	  if (ptrace (PTRACE_SETREGSET, tid, NT_LARCH_LBT, (long) &iov) < 0) | 
 | 	    perror_with_name (_("Couldn't set NT_LARCH_LBT registers")); | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 | /* Implement the "fetch_registers" target_ops method.  */ | 
 |  | 
 | void | 
 | loongarch_linux_nat_target::fetch_registers (struct regcache *regcache, | 
 | 					     int regnum) | 
 | { | 
 |   pid_t tid = get_ptrace_pid (regcache->ptid ()); | 
 |  | 
 |   fetch_gregs_from_thread(regcache, regnum, tid); | 
 |   fetch_fpregs_from_thread(regcache, regnum, tid); | 
 |   fetch_lsxregs_from_thread(regcache, regnum, tid); | 
 |   fetch_lasxregs_from_thread(regcache, regnum, tid); | 
 |   fetch_lbt_from_thread (regcache, regnum, tid); | 
 | } | 
 |  | 
 | /* Implement the "store_registers" target_ops method.  */ | 
 |  | 
 | void | 
 | loongarch_linux_nat_target::store_registers (struct regcache *regcache, | 
 | 					     int regnum) | 
 | { | 
 |   pid_t tid = get_ptrace_pid (regcache->ptid ()); | 
 |  | 
 |   store_gregs_to_thread (regcache, regnum, tid); | 
 |   store_fpregs_to_thread(regcache, regnum, tid); | 
 |   store_lsxregs_to_thread(regcache, regnum, tid); | 
 |   store_lasxregs_to_thread(regcache, regnum, tid); | 
 |   store_lbt_to_thread (regcache, regnum, tid); | 
 | } | 
 |  | 
 | /* Return the address in the core dump or inferior of register REGNO.  */ | 
 |  | 
 | CORE_ADDR | 
 | loongarch_linux_nat_target::register_u_offset (struct gdbarch *gdbarch, | 
 | 					       int regnum, int store_p) | 
 | { | 
 |   if (regnum >= 0 && regnum < 32) | 
 |     return regnum; | 
 |   else if (regnum == LOONGARCH_PC_REGNUM) | 
 |     return LOONGARCH_PC_REGNUM; | 
 |   else | 
 |     return -1; | 
 | } | 
 |  | 
 | static loongarch_linux_nat_target the_loongarch_linux_nat_target; | 
 |  | 
 | /* Wrapper functions.  These are only used by libthread_db.  */ | 
 |  | 
 | void | 
 | supply_gregset (struct regcache *regcache, const gdb_gregset_t *gregset) | 
 | { | 
 |   loongarch_gregset.supply_regset (nullptr, regcache, -1, gregset, | 
 | 				   sizeof (gdb_gregset_t)); | 
 | } | 
 |  | 
 | void | 
 | fill_gregset (const struct regcache *regcache, gdb_gregset_t *gregset, | 
 | 	      int regnum) | 
 | { | 
 |   loongarch_gregset.collect_regset (nullptr, regcache, regnum, gregset, | 
 | 				    sizeof (gdb_gregset_t)); | 
 | } | 
 |  | 
 | void | 
 | supply_fpregset (struct regcache *regcache, const gdb_fpregset_t *fpregset) | 
 | { | 
 |   loongarch_fpregset.supply_regset (nullptr, regcache, -1, fpregset, | 
 | 				    sizeof (gdb_fpregset_t)); | 
 | } | 
 |  | 
 | void | 
 | fill_fpregset (const struct regcache *regcache, gdb_fpregset_t *fpregset, | 
 | 	       int regnum) | 
 | { | 
 |   loongarch_fpregset.collect_regset (nullptr, regcache, regnum, fpregset, | 
 | 				     sizeof (gdb_fpregset_t)); | 
 | } | 
 |  | 
 | /* 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) | 
 |     { | 
 |       if (loongarch_num_bp_regs == 0) | 
 | 	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; | 
 |  | 
 |   /* Check if the address matches any watched address.  */ | 
 |   state = loongarch_get_debug_reg_state (inferior_ptid.pid ()); | 
 |  | 
 |   return | 
 |     loongarch_stopped_data_address (state, (CORE_ADDR) siginfo.si_addr, 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); | 
 | } | 
 |  | 
 | /* 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 | 
 | 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 (); | 
 | void | 
 | _initialize_loongarch_linux_nat () | 
 | { | 
 |   linux_target = &the_loongarch_linux_nat_target; | 
 |   add_inf_child_target (&the_loongarch_linux_nat_target); | 
 |  | 
 |   /* Add a maintenance command to enable printing the LoongArch internal | 
 |      debug registers mirror variables.  */ | 
 |   add_setshow_boolean_cmd ("show-debug-regs", class_maintenance, | 
 | 			   &show_debug_regs, _("\ | 
 | Set whether to show the LoongArch debug registers state."), _("\ | 
 | Show whether to show the LoongArch debug registers state."), _("\ | 
 | 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); | 
 | } |