| /* 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 (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_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 = is_watchpoint ? &info->dr_changed_wp | 
 | 		   : &info->dr_changed_bp; | 
 |   dr_changed = *dr_changed_ptr; | 
 |  | 
 |   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.  */ | 
 |   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_bp=0x%s, " | 
 | 		    "dr_changed_wp=0x%s\n", tid, | 
 | 		    phex (info->dr_changed_bp, 8), | 
 | 		    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 (®s, 0, sizeof (regs)); | 
 |   iov.iov_base = ®s; | 
 |   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; | 
 |  | 
 |   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, | 
 | 	     watchpoint ? NT_LOONGARCH_HW_WATCH : NT_LOONGARCH_HW_BREAK, | 
 | 	     (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; | 
 |     } | 
 |  | 
 |   /* 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 | 
 |    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); | 
 | } |