/* 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 . */ #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 /* The order in which and are included can be important. often declares various PTRACE_* enums. often defines preprocessor constants for these very same symbols. When that's the case, build errors will result when is included before . */ #include #include #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 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); }