| /* 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 "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; |
| } |