/* 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 "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_bp_regs; 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_PLV3_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 { 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); /* Find an existing or free register in our cache. */ idx = -1; for (i = 0; i < num_regs; ++i) { if ((dr_ctrl_p[i] & CTRL_PLV3_ENABLE) == 0) // 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_PLV3_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 { 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); /* 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_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, 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 : loongarch_num_bp_regs; if (count == 0) return false; 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) 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 ("\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", 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; }