blob: 089f3bd49a9377582d1fbe54ea4e9ef7f6880674 [file] [log] [blame]
/* 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;
}