forked from Imagelibrary/binutils-gdb
gdb: LoongArch: Add support for hardware watchpoint
LoongArch defines hardware watchpoint functions for load/store operations. After the software configures the watchpoints for load/store, the processor hardware will monitor the access addresses of the load/store operations and trigger watchpoint exception when the watchpoint setting conditions are met. After this patch, watch/rwatch/awatch command are supported. Refer to the following document for hardware watchpoint. https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#control-and-status-registers-related-to-watchpoints A simple test is as follows: lihui@bogon:~$ cat test.c #include <stdio.h> int a = 0; int main() { printf("start test\n"); a = 1; printf("a = %d\n", a); printf("end test\n"); return 0; } lihui@bogon:~$ gcc -g test.c -o test without this patch: lihui@bogon:~$ gdb test ... (gdb) start ... Temporary breakpoint 1, main () at test.c:5 5 printf("start test\n"); (gdb) awatch a Target does not support this type of hardware watchpoint. ... with this patch: lihui@bogon:~$ gdb test ... (gdb) start ... Temporary breakpoint 1, main () at test.c:5 5 printf("start test\n"); (gdb) awatch a Hardware access (read/write) watchpoint 2: a (gdb) c Continuing. start test Hardware access (read/write) watchpoint 2: a Old value = 0 New value = 1 main () at test.c:7 7 printf("a = %d\n", a); (gdb) c Continuing. Hardware access (read/write) watchpoint 2: a Value = 1 0x00000001200006e0 in main () at test.c:7 7 printf("a = %d\n", a); (gdb) c Continuing. a = 1 end test [Inferior 1 (process 22250) exited normally] Signed-off-by: Hui Li <lihui@loongson.cn> Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
This commit is contained in:
@@ -1608,6 +1608,9 @@ HFILES_NO_SRCDIR = \
|
|||||||
nat/linux-personality.h \
|
nat/linux-personality.h \
|
||||||
nat/linux-ptrace.h \
|
nat/linux-ptrace.h \
|
||||||
nat/linux-waitpid.h \
|
nat/linux-waitpid.h \
|
||||||
|
nat/loongarch-hw-point.h \
|
||||||
|
nat/loongarch-linux.h \
|
||||||
|
nat/loongarch-linux-hw-point.h \
|
||||||
nat/mips-linux-watch.h \
|
nat/mips-linux-watch.h \
|
||||||
nat/ppc-linux.h \
|
nat/ppc-linux.h \
|
||||||
nat/x86-cpuid.h \
|
nat/x86-cpuid.h \
|
||||||
|
|||||||
@@ -266,7 +266,9 @@ case ${gdb_host} in
|
|||||||
;;
|
;;
|
||||||
loongarch)
|
loongarch)
|
||||||
# Host: LoongArch, running GNU/Linux.
|
# Host: LoongArch, running GNU/Linux.
|
||||||
NATDEPFILES="${NATDEPFILES} loongarch-linux-nat.o linux-nat-trad.o"
|
NATDEPFILES="${NATDEPFILES} loongarch-linux-nat.o linux-nat-trad.o \
|
||||||
|
nat/loongarch-hw-point.o nat/loongarch-linux.o \
|
||||||
|
nat/loongarch-linux-hw-point.o"
|
||||||
;;
|
;;
|
||||||
m32r)
|
m32r)
|
||||||
# Host: M32R based machine running GNU/Linux
|
# Host: M32R based machine running GNU/Linux
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
#include "linux-nat-trad.h"
|
#include "linux-nat-trad.h"
|
||||||
#include "loongarch-tdep.h"
|
#include "loongarch-tdep.h"
|
||||||
#include "nat/gdb_ptrace.h"
|
#include "nat/gdb_ptrace.h"
|
||||||
|
#include "nat/loongarch-hw-point.h"
|
||||||
|
#include "nat/loongarch-linux.h"
|
||||||
|
#include "nat/loongarch-linux-hw-point.h"
|
||||||
#include "target-descriptions.h"
|
#include "target-descriptions.h"
|
||||||
|
|
||||||
#include <asm/ptrace.h>
|
#include <asm/ptrace.h>
|
||||||
@@ -37,6 +40,37 @@ public:
|
|||||||
void fetch_registers (struct regcache *, int) override;
|
void fetch_registers (struct regcache *, int) override;
|
||||||
void store_registers (struct regcache *, int) override;
|
void store_registers (struct regcache *, int) override;
|
||||||
|
|
||||||
|
int can_use_hw_breakpoint (enum bptype type, int cnt, int othertype) override;
|
||||||
|
int region_ok_for_hw_watchpoint (CORE_ADDR addr, int len) override;
|
||||||
|
|
||||||
|
int insert_watchpoint (CORE_ADDR addr, int len, enum target_hw_bp_type type,
|
||||||
|
struct expression *cond) override;
|
||||||
|
int remove_watchpoint (CORE_ADDR addr, int len, enum target_hw_bp_type type,
|
||||||
|
struct expression *cond) override;
|
||||||
|
bool watchpoint_addr_within_range (CORE_ADDR addr, CORE_ADDR start,
|
||||||
|
int length) override;
|
||||||
|
|
||||||
|
/* Add our hardware breakpoint and watchpoint implementation. */
|
||||||
|
bool stopped_by_watchpoint () override;
|
||||||
|
bool stopped_data_address (CORE_ADDR *) override;
|
||||||
|
|
||||||
|
/* Override the GNU/Linux inferior startup hook. */
|
||||||
|
void post_startup_inferior (ptid_t) override;
|
||||||
|
|
||||||
|
/* Override the GNU/Linux post attach hook. */
|
||||||
|
void post_attach (int pid) override;
|
||||||
|
|
||||||
|
/* These three defer to common nat/ code. */
|
||||||
|
void low_new_thread (struct lwp_info *lp) override
|
||||||
|
{ loongarch_linux_new_thread (lp); }
|
||||||
|
void low_delete_thread (struct arch_lwp_info *lp) override
|
||||||
|
{ loongarch_linux_delete_thread (lp); }
|
||||||
|
void low_prepare_to_resume (struct lwp_info *lp) override
|
||||||
|
{ loongarch_linux_prepare_to_resume (lp); }
|
||||||
|
|
||||||
|
void low_new_fork (struct lwp_info *parent, pid_t child_pid) override;
|
||||||
|
void low_forget_process (pid_t pid) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/* Override linux_nat_trad_target methods. */
|
/* Override linux_nat_trad_target methods. */
|
||||||
CORE_ADDR register_u_offset (struct gdbarch *gdbarch, int regnum,
|
CORE_ADDR register_u_offset (struct gdbarch *gdbarch, int regnum,
|
||||||
@@ -406,6 +440,251 @@ fill_fpregset (const struct regcache *regcache, gdb_fpregset_t *fpregset,
|
|||||||
sizeof (gdb_fpregset_t));
|
sizeof (gdb_fpregset_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Helper for the "stopped_data_address" target method. Returns TRUE
|
||||||
|
if a hardware watchpoint trap at ADDR_TRAP matches a set watchpoint.
|
||||||
|
The address of the matched watchpoint is returned in *ADDR_P. */
|
||||||
|
|
||||||
|
static bool
|
||||||
|
loongarch_stopped_data_address (const struct loongarch_debug_reg_state *state,
|
||||||
|
CORE_ADDR addr_trap, CORE_ADDR *addr_p)
|
||||||
|
{
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = loongarch_num_wp_regs - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
const CORE_ADDR addr_watch = state->dr_addr_wp[i];
|
||||||
|
|
||||||
|
if (state->dr_ref_count_wp[i]
|
||||||
|
&& DR_CONTROL_ENABLED (state->dr_ctrl_wp[i])
|
||||||
|
&& addr_trap == addr_watch)
|
||||||
|
{
|
||||||
|
*addr_p = addr_watch;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Returns the number of hardware watchpoints of type TYPE that we can
|
||||||
|
set. Value is positive if we can set CNT watchpoints, zero if
|
||||||
|
setting watchpoints of type TYPE is not supported, and negative if
|
||||||
|
CNT is more than the maximum number of watchpoints of type TYPE
|
||||||
|
that we can support. TYPE is one of bp_hardware_watchpoint,
|
||||||
|
bp_read_watchpoint, bp_write_watchpoint, or bp_hardware_breakpoint.
|
||||||
|
CNT is the number of such watchpoints used so far (including this
|
||||||
|
one). OTHERTYPE is non-zero if other types of watchpoints are
|
||||||
|
currently enabled. */
|
||||||
|
|
||||||
|
int
|
||||||
|
loongarch_linux_nat_target::can_use_hw_breakpoint (enum bptype type, int cnt,
|
||||||
|
int othertype)
|
||||||
|
{
|
||||||
|
if (type == bp_hardware_watchpoint || type == bp_read_watchpoint
|
||||||
|
|| type == bp_access_watchpoint || type == bp_watchpoint)
|
||||||
|
{
|
||||||
|
if (loongarch_num_wp_regs == 0)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (type == bp_hardware_breakpoint)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
gdb_assert_not_reached ("unexpected breakpoint type");
|
||||||
|
|
||||||
|
/* We always return 1 here because we don't have enough information
|
||||||
|
about possible overlap of addresses that they want to watch. As an
|
||||||
|
extreme example, consider the case where all the watchpoints watch
|
||||||
|
the same address and the same region length: then we can handle a
|
||||||
|
virtually unlimited number of watchpoints, due to debug register
|
||||||
|
sharing implemented via reference counts. */
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
loongarch_linux_nat_target::region_ok_for_hw_watchpoint (CORE_ADDR addr,
|
||||||
|
int len)
|
||||||
|
{
|
||||||
|
return loongarch_region_ok_for_watchpoint (addr, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Insert a watchpoint to watch a memory region which starts at
|
||||||
|
address ADDR and whose length is LEN bytes. Watch memory accesses
|
||||||
|
of the type TYPE. Return 0 on success, -1 on failure. */
|
||||||
|
|
||||||
|
int
|
||||||
|
loongarch_linux_nat_target::insert_watchpoint (CORE_ADDR addr, int len,
|
||||||
|
enum target_hw_bp_type type,
|
||||||
|
struct expression *cond)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct loongarch_debug_reg_state *state
|
||||||
|
= loongarch_get_debug_reg_state (inferior_ptid.pid ());
|
||||||
|
|
||||||
|
if (show_debug_regs)
|
||||||
|
gdb_printf (gdb_stdlog,
|
||||||
|
"insert_watchpoint on entry (addr=0x%08lx, len=%d)\n",
|
||||||
|
(unsigned long) addr, len);
|
||||||
|
|
||||||
|
gdb_assert (type != hw_execute);
|
||||||
|
|
||||||
|
ret = loongarch_handle_watchpoint (type, addr, len, 1 /* is_insert */,
|
||||||
|
inferior_ptid, state);
|
||||||
|
|
||||||
|
if (show_debug_regs)
|
||||||
|
{
|
||||||
|
loongarch_show_debug_reg_state (state,
|
||||||
|
"insert_watchpoint", addr, len, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove a watchpoint that watched the memory region which starts at
|
||||||
|
address ADDR, whose length is LEN bytes, and for accesses of the
|
||||||
|
type TYPE. Return 0 on success, -1 on failure. */
|
||||||
|
|
||||||
|
int
|
||||||
|
loongarch_linux_nat_target::remove_watchpoint (CORE_ADDR addr, int len,
|
||||||
|
enum target_hw_bp_type type,
|
||||||
|
struct expression *cond)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct loongarch_debug_reg_state *state
|
||||||
|
= loongarch_get_debug_reg_state (inferior_ptid.pid ());
|
||||||
|
|
||||||
|
if (show_debug_regs)
|
||||||
|
gdb_printf (gdb_stdlog,
|
||||||
|
"remove_watchpoint on entry (addr=0x%08lx, len=%d)\n",
|
||||||
|
(unsigned long) addr, len);
|
||||||
|
|
||||||
|
gdb_assert (type != hw_execute);
|
||||||
|
|
||||||
|
ret = loongarch_handle_watchpoint (type, addr, len, 0 /* is_insert */,
|
||||||
|
inferior_ptid, state);
|
||||||
|
|
||||||
|
if (show_debug_regs)
|
||||||
|
{
|
||||||
|
loongarch_show_debug_reg_state (state,
|
||||||
|
"remove_watchpoint", addr, len, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
loongarch_linux_nat_target::watchpoint_addr_within_range (CORE_ADDR addr,
|
||||||
|
CORE_ADDR start,
|
||||||
|
int length)
|
||||||
|
{
|
||||||
|
return start <= addr && start + length - 1 >= addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Implement the "stopped_data_address" target_ops method. */
|
||||||
|
|
||||||
|
bool
|
||||||
|
loongarch_linux_nat_target::stopped_data_address (CORE_ADDR *addr_p)
|
||||||
|
{
|
||||||
|
siginfo_t siginfo;
|
||||||
|
struct loongarch_debug_reg_state *state;
|
||||||
|
|
||||||
|
if (!linux_nat_get_siginfo (inferior_ptid, &siginfo))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* This must be a hardware breakpoint. */
|
||||||
|
if (siginfo.si_signo != SIGTRAP || (siginfo.si_code & 0xffff) != TRAP_HWBKPT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Make sure to ignore the top byte, otherwise we may not recognize a
|
||||||
|
hardware watchpoint hit. The stopped data addresses coming from the
|
||||||
|
kernel can potentially be tagged addresses. */
|
||||||
|
struct gdbarch *gdbarch = thread_architecture (inferior_ptid);
|
||||||
|
const CORE_ADDR addr_trap
|
||||||
|
= gdbarch_remove_non_address_bits (gdbarch, (CORE_ADDR) siginfo.si_addr);
|
||||||
|
|
||||||
|
/* Check if the address matches any watched address. */
|
||||||
|
state = loongarch_get_debug_reg_state (inferior_ptid.pid ());
|
||||||
|
|
||||||
|
return loongarch_stopped_data_address (state, addr_trap, addr_p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implement the "stopped_by_watchpoint" target_ops method. */
|
||||||
|
|
||||||
|
bool
|
||||||
|
loongarch_linux_nat_target::stopped_by_watchpoint ()
|
||||||
|
{
|
||||||
|
CORE_ADDR addr;
|
||||||
|
|
||||||
|
return stopped_data_address (&addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implement the virtual inf_ptrace_target::post_startup_inferior method. */
|
||||||
|
|
||||||
|
void
|
||||||
|
loongarch_linux_nat_target::post_startup_inferior (ptid_t ptid)
|
||||||
|
{
|
||||||
|
low_forget_process (ptid.pid ());
|
||||||
|
loongarch_linux_get_debug_reg_capacity (ptid.pid ());
|
||||||
|
linux_nat_target::post_startup_inferior (ptid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implement the "post_attach" target_ops method. */
|
||||||
|
|
||||||
|
void
|
||||||
|
loongarch_linux_nat_target::post_attach (int pid)
|
||||||
|
{
|
||||||
|
low_forget_process (pid);
|
||||||
|
/* Get the hardware debug register capacity. If
|
||||||
|
loongarch_linux_get_debug_reg_capacity is not called
|
||||||
|
(as it is in loongarch_linux_child_post_startup_inferior) then
|
||||||
|
software watchpoints will be used instead of hardware
|
||||||
|
watchpoints when attaching to a target. */
|
||||||
|
loongarch_linux_get_debug_reg_capacity (pid);
|
||||||
|
linux_nat_target::post_attach (pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* linux_nat_new_fork hook. */
|
||||||
|
|
||||||
|
void
|
||||||
|
loongarch_linux_nat_target::low_new_fork (struct lwp_info *parent,
|
||||||
|
pid_t child_pid)
|
||||||
|
{
|
||||||
|
pid_t parent_pid;
|
||||||
|
struct loongarch_debug_reg_state *parent_state;
|
||||||
|
struct loongarch_debug_reg_state *child_state;
|
||||||
|
|
||||||
|
/* NULL means no watchpoint has ever been set in the parent. In
|
||||||
|
that case, there's nothing to do. */
|
||||||
|
if (parent->arch_private == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* GDB core assumes the child inherits the watchpoints/hw
|
||||||
|
breakpoints of the parent, and will remove them all from the
|
||||||
|
forked off process. Copy the debug registers mirrors into the
|
||||||
|
new process so that all breakpoints and watchpoints can be
|
||||||
|
removed together. */
|
||||||
|
|
||||||
|
parent_pid = parent->ptid.pid ();
|
||||||
|
parent_state = loongarch_get_debug_reg_state (parent_pid);
|
||||||
|
child_state = loongarch_get_debug_reg_state (child_pid);
|
||||||
|
*child_state = *parent_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called whenever GDB is no longer debugging process PID. It deletes
|
||||||
|
data structures that keep track of debug register state. */
|
||||||
|
|
||||||
|
void
|
||||||
|
loongarch_linux_nat_target::low_forget_process (pid_t pid)
|
||||||
|
{
|
||||||
|
loongarch_remove_debug_reg_state (pid);
|
||||||
|
}
|
||||||
|
|
||||||
/* Initialize LoongArch Linux native support. */
|
/* Initialize LoongArch Linux native support. */
|
||||||
|
|
||||||
void _initialize_loongarch_linux_nat ();
|
void _initialize_loongarch_linux_nat ();
|
||||||
|
|||||||
@@ -1869,6 +1869,7 @@ loongarch_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
|
|||||||
set_gdbarch_software_single_step (gdbarch, loongarch_software_single_step);
|
set_gdbarch_software_single_step (gdbarch, loongarch_software_single_step);
|
||||||
set_gdbarch_breakpoint_kind_from_pc (gdbarch, loongarch_breakpoint::kind_from_pc);
|
set_gdbarch_breakpoint_kind_from_pc (gdbarch, loongarch_breakpoint::kind_from_pc);
|
||||||
set_gdbarch_sw_breakpoint_from_kind (gdbarch, loongarch_breakpoint::bp_from_kind);
|
set_gdbarch_sw_breakpoint_from_kind (gdbarch, loongarch_breakpoint::bp_from_kind);
|
||||||
|
set_gdbarch_have_nonsteppable_watchpoint (gdbarch, 1);
|
||||||
|
|
||||||
/* Frame unwinders. Use DWARF debug info if available, otherwise use our own unwinder. */
|
/* Frame unwinders. Use DWARF debug info if available, otherwise use our own unwinder. */
|
||||||
set_gdbarch_dwarf2_reg_to_regnum (gdbarch, loongarch_dwarf2_reg_to_regnum);
|
set_gdbarch_dwarf2_reg_to_regnum (gdbarch, loongarch_dwarf2_reg_to_regnum);
|
||||||
|
|||||||
293
gdb/nat/loongarch-hw-point.c
Normal file
293
gdb/nat/loongarch-hw-point.c
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
/* 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_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
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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 : 0;
|
||||||
|
if (count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const CORE_ADDR *addr = watchpoint ? state->dr_addr_wp : 0;
|
||||||
|
const unsigned int *ctrl = watchpoint ? state->dr_ctrl_wp : 0;
|
||||||
|
|
||||||
|
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 ("\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;
|
||||||
|
}
|
||||||
92
gdb/nat/loongarch-hw-point.h
Normal file
92
gdb/nat/loongarch-hw-point.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/* 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/>. */
|
||||||
|
|
||||||
|
#ifndef NAT_LOONGARCH_HW_POINT_H
|
||||||
|
#define NAT_LOONGARCH_HW_POINT_H
|
||||||
|
|
||||||
|
/* Macro definitions, data structures, and code for the hardware
|
||||||
|
breakpoint and hardware watchpoint support follow. We use the
|
||||||
|
following abbreviations throughout the code:
|
||||||
|
|
||||||
|
hw - hardware
|
||||||
|
bp - breakpoint
|
||||||
|
wp - watchpoint */
|
||||||
|
|
||||||
|
/* Maximum number of hardware breakpoint and watchpoint registers.
|
||||||
|
Neither of these values may exceed the width of dr_changed_t
|
||||||
|
measured in bits. */
|
||||||
|
|
||||||
|
#define LOONGARCH_HWP_MAX_NUM 8
|
||||||
|
|
||||||
|
|
||||||
|
/* The maximum length of a memory region that can be watched by one
|
||||||
|
hardware watchpoint register. */
|
||||||
|
|
||||||
|
#define LOONGARCH_HWP_MAX_LEN_PER_REG 8
|
||||||
|
#define CTRL_PLV3_ENABLE 0x10
|
||||||
|
|
||||||
|
#define DR_CONTROL_ENABLED(ctrl) ((ctrl & CTRL_PLV3_ENABLE) == CTRL_PLV3_ENABLE)
|
||||||
|
|
||||||
|
/* Structure for managing the hardware breakpoint/watchpoint resources.
|
||||||
|
DR_ADDR_* stores the address, DR_CTRL_* stores the control register
|
||||||
|
content, and DR_REF_COUNT_* counts the numbers of references to the
|
||||||
|
corresponding bp/wp, by which way the limited hardware resources
|
||||||
|
are not wasted on duplicated bp/wp settings (though so far gdb has
|
||||||
|
done a good job by not sending duplicated bp/wp requests). */
|
||||||
|
|
||||||
|
struct loongarch_debug_reg_state
|
||||||
|
{
|
||||||
|
/* hardware watchpoint */
|
||||||
|
CORE_ADDR dr_addr_wp[LOONGARCH_HWP_MAX_NUM];
|
||||||
|
unsigned int dr_ctrl_wp[LOONGARCH_HWP_MAX_NUM];
|
||||||
|
unsigned int dr_ref_count_wp[LOONGARCH_HWP_MAX_NUM];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int loongarch_num_wp_regs;
|
||||||
|
|
||||||
|
/* Invoked when IDXth breakpoint/watchpoint register pair needs to be
|
||||||
|
updated. */
|
||||||
|
|
||||||
|
void loongarch_notify_debug_reg_change (ptid_t ptid, int is_watchpoint,
|
||||||
|
unsigned int idx);
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* Return TRUE if there are any hardware breakpoints. If WATCHPOINT is TRUE,
|
||||||
|
check hardware watchpoints instead. */
|
||||||
|
|
||||||
|
bool loongarch_any_set_debug_regs_state (loongarch_debug_reg_state *state,
|
||||||
|
bool watchpoint);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
#endif /* NAT_LOONGARCH_HW_POINT_H */
|
||||||
227
gdb/nat/loongarch-linux-hw-point.c
Normal file
227
gdb/nat/loongarch-linux-hw-point.c
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
/* 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 (!is_watchpoint)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
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_wp=0x%s\n",
|
||||||
|
tid, phex (info->dr_changed_wp, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
dr_changed_ptr = &info->dr_changed_wp;
|
||||||
|
dr_changed = *dr_changed_ptr;
|
||||||
|
|
||||||
|
gdb_assert (idx >= 0 && idx <= loongarch_num_wp_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_wp=0x%s\n",
|
||||||
|
tid, 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 : 0;
|
||||||
|
addr = watchpoint ? state->dr_addr_wp : 0;
|
||||||
|
ctrl = watchpoint ? state->dr_ctrl_wp : 0;
|
||||||
|
|
||||||
|
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, NT_LOONGARCH_HW_WATCH, (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
125
gdb/nat/loongarch-linux-hw-point.h
Normal file
125
gdb/nat/loongarch-linux-hw-point.h
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/* 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/>. */
|
||||||
|
|
||||||
|
#ifndef NAT_LOONGARCH_LINUX_HW_POINT_H
|
||||||
|
#define NAT_LOONGARCH_LINUX_HW_POINT_H
|
||||||
|
|
||||||
|
#include "gdbsupport/break-common.h" /* For enum target_hw_bp_type. */
|
||||||
|
|
||||||
|
#include "nat/loongarch-hw-point.h"
|
||||||
|
|
||||||
|
struct loongarch_user_watch_state {
|
||||||
|
uint64_t dbg_info;
|
||||||
|
struct {
|
||||||
|
uint64_t addr;
|
||||||
|
uint64_t mask;
|
||||||
|
uint32_t ctrl;
|
||||||
|
uint32_t pad;
|
||||||
|
} dbg_regs[8];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Macros to extract fields from the hardware debug information word. */
|
||||||
|
#define LOONGARCH_DEBUG_NUM_SLOTS(x) ((x) & 0xffff)
|
||||||
|
|
||||||
|
/* Each bit of a variable of this type is used to indicate whether a
|
||||||
|
hardware breakpoint or watchpoint setting has been changed since
|
||||||
|
the last update.
|
||||||
|
|
||||||
|
Bit N corresponds to the Nth hardware breakpoint or watchpoint
|
||||||
|
setting which is managed in loongarch_debug_reg_state, where N is
|
||||||
|
valid between 0 and the total number of the hardware breakpoint or
|
||||||
|
watchpoint debug registers minus 1.
|
||||||
|
|
||||||
|
When bit N is 1, the corresponding breakpoint or watchpoint setting
|
||||||
|
has changed, and therefore the corresponding hardware debug
|
||||||
|
register needs to be updated via the ptrace interface.
|
||||||
|
|
||||||
|
In the per-thread arch-specific data area, we define two such
|
||||||
|
variables for per-thread hardware breakpoint and watchpoint
|
||||||
|
settings respectively.
|
||||||
|
|
||||||
|
This type is part of the mechanism which helps reduce the number of
|
||||||
|
ptrace calls to the kernel, i.e. avoid asking the kernel to write
|
||||||
|
to the debug registers with unchanged values. */
|
||||||
|
|
||||||
|
typedef ULONGEST dr_changed_t;
|
||||||
|
|
||||||
|
/* Set each of the lower M bits of X to 1; assert X is wide enough. */
|
||||||
|
|
||||||
|
#define DR_MARK_ALL_CHANGED(x, m) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
gdb_assert (sizeof ((x)) * 8 >= (m)); \
|
||||||
|
(x) = (((dr_changed_t)1 << (m)) - 1); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DR_MARK_N_CHANGED(x, n) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
(x) |= ((dr_changed_t)1 << (n)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DR_CLEAR_CHANGED(x) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
(x) = 0; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DR_HAS_CHANGED(x) ((x) != 0)
|
||||||
|
#define DR_N_HAS_CHANGED(x, n) ((x) & ((dr_changed_t)1 << (n)))
|
||||||
|
|
||||||
|
/* Per-thread arch-specific data we want to keep. */
|
||||||
|
|
||||||
|
struct arch_lwp_info
|
||||||
|
{
|
||||||
|
/* When bit N is 1, it indicates the Nth hardware breakpoint or
|
||||||
|
watchpoint register pair needs to be updated when the thread is
|
||||||
|
resumed; see loongarch_linux_prepare_to_resume. */
|
||||||
|
dr_changed_t dr_changed_wp;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
/* Get the hardware debug register capacity information from the
|
||||||
|
process represented by TID. */
|
||||||
|
|
||||||
|
void loongarch_linux_get_debug_reg_capacity (int tid);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
/* Remove any existing per-process debug state for process PID. */
|
||||||
|
|
||||||
|
void loongarch_remove_debug_reg_state (pid_t pid);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* NAT_LOONGARCH_LINUX_HW_POINT_H */
|
||||||
87
gdb/nat/loongarch-linux.c
Normal file
87
gdb/nat/loongarch-linux.c
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/* 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 "nat/linux-nat.h"
|
||||||
|
#include "nat/loongarch-linux-hw-point.h"
|
||||||
|
#include "nat/loongarch-linux.h"
|
||||||
|
|
||||||
|
#include "elf/common.h"
|
||||||
|
#include "nat/gdb_ptrace.h"
|
||||||
|
#include <asm/ptrace.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
|
||||||
|
/* Called when resuming a thread LWP.
|
||||||
|
The hardware debug registers are updated when there is any change. */
|
||||||
|
|
||||||
|
void
|
||||||
|
loongarch_linux_prepare_to_resume (struct lwp_info *lwp)
|
||||||
|
{
|
||||||
|
struct arch_lwp_info *info = lwp_arch_private_info (lwp);
|
||||||
|
|
||||||
|
/* NULL means this is the main thread still going through the shell,
|
||||||
|
or, no watchpoint has been set yet. In that case, there's
|
||||||
|
nothing to do. */
|
||||||
|
if (info == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (DR_HAS_CHANGED (info->dr_changed_wp))
|
||||||
|
{
|
||||||
|
ptid_t ptid = ptid_of_lwp (lwp);
|
||||||
|
int tid = ptid.lwp ();
|
||||||
|
struct loongarch_debug_reg_state *state
|
||||||
|
= loongarch_get_debug_reg_state (ptid.pid ());
|
||||||
|
|
||||||
|
if (show_debug_regs)
|
||||||
|
debug_printf ("prepare_to_resume thread %d\n", tid);
|
||||||
|
|
||||||
|
loongarch_linux_set_debug_regs (state, tid, 1);
|
||||||
|
DR_CLEAR_CHANGED (info->dr_changed_wp);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Function to call when a new thread is detected. */
|
||||||
|
|
||||||
|
void
|
||||||
|
loongarch_linux_new_thread (struct lwp_info *lwp)
|
||||||
|
{
|
||||||
|
ptid_t ptid = ptid_of_lwp (lwp);
|
||||||
|
struct loongarch_debug_reg_state *state
|
||||||
|
= loongarch_get_debug_reg_state (ptid.pid ());
|
||||||
|
struct arch_lwp_info *info = XCNEW (struct arch_lwp_info);
|
||||||
|
|
||||||
|
/* If there are hardware breakpoints/watchpoints in the process then mark that
|
||||||
|
all the hardware breakpoint/watchpoint register pairs for this thread need
|
||||||
|
to be initialized (with data from arch_process_info.debug_reg_state). */
|
||||||
|
if (loongarch_any_set_debug_regs_state (state, true))
|
||||||
|
DR_MARK_ALL_CHANGED (info->dr_changed_wp, loongarch_num_wp_regs);
|
||||||
|
|
||||||
|
lwp_set_arch_private_info (lwp, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See nat/loongarch-linux.h. */
|
||||||
|
|
||||||
|
void
|
||||||
|
loongarch_linux_delete_thread (struct arch_lwp_info *arch_lwp)
|
||||||
|
{
|
||||||
|
xfree (arch_lwp);
|
||||||
|
}
|
||||||
42
gdb/nat/loongarch-linux.h
Normal file
42
gdb/nat/loongarch-linux.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/* 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/>. */
|
||||||
|
|
||||||
|
#ifndef NAT_LOONGARCH_LINUX_H
|
||||||
|
#define NAT_LOONGARCH_LINUX_H
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
/* Defines ps_err_e, struct ps_prochandle. */
|
||||||
|
#include "gdb_proc_service.h"
|
||||||
|
|
||||||
|
/* Called when resuming a thread LWP.
|
||||||
|
The hardware debug registers are updated when there is any change. */
|
||||||
|
|
||||||
|
void loongarch_linux_prepare_to_resume (struct lwp_info *lwp);
|
||||||
|
|
||||||
|
/* Function to call when a new thread is detected. */
|
||||||
|
|
||||||
|
void loongarch_linux_new_thread (struct lwp_info *lwp);
|
||||||
|
|
||||||
|
/* Function to call when a thread is being deleted. */
|
||||||
|
|
||||||
|
void loongarch_linux_delete_thread (struct arch_lwp_info *arch_lwp);
|
||||||
|
|
||||||
|
#endif /* LOONGARCH_LINUX_H */
|
||||||
@@ -751,6 +751,8 @@
|
|||||||
/* note name must be "LINUX". */
|
/* note name must be "LINUX". */
|
||||||
#define NT_LARCH_LBT 0xa04 /* LoongArch Binary Translation registers */
|
#define NT_LARCH_LBT 0xa04 /* LoongArch Binary Translation registers */
|
||||||
/* note name must be "CORE". */
|
/* note name must be "CORE". */
|
||||||
|
#define NT_LOONGARCH_HW_WATCH 0xa06 /* LoongArch hardware watchpoint registers */
|
||||||
|
/* note name must be "LINUX". */
|
||||||
#define NT_RISCV_CSR 0x900 /* RISC-V Control and Status Registers */
|
#define NT_RISCV_CSR 0x900 /* RISC-V Control and Status Registers */
|
||||||
/* note name must be "LINUX". */
|
/* note name must be "LINUX". */
|
||||||
#define NT_SIGINFO 0x53494749 /* Fields of siginfo_t. */
|
#define NT_SIGINFO 0x53494749 /* Fields of siginfo_t. */
|
||||||
|
|||||||
Reference in New Issue
Block a user