gdbserver: LoongArch: Add hardware watchpoint/breakpoint support

LoongArch defines hardware watchpoint functions for fetch and load/store
operations, the related support for gdb was added in the following two

  commit c1cdee0e2c17 ("gdb: LoongArch: Add support for hardware watchpoint")
  commit 6ced1278fc00 ("gdb: LoongArch: Add support for hardware breakpoint")

Now, add hardware watchpoint and breakpoint support for gdbserver on
LoongArch.

Here is a simple example

$ cat test.c
  #include <stdio.h>
  int a = 0;
  int b = 0;
  int main()
  {
    printf("start test\n");
    a = 1;
    printf("a = %d\n", a);
    a = 2;
    printf("a = %d\n", a);
    b = 2;
    printf("b = %d\n", b);
    return 0;
  }
$ gcc -g test.c -o test

Execute on the target machine:

$ gdbserver 192.168.1.100:1234 ./test

Execute on the host machine:

$ gdb ./test
...
(gdb) target remote 192.168.1.100:1234
...
(gdb) b main
Breakpoint 1 at 0x1200006b8: file test.c, line 6.
(gdb) c
Continuing.
...
Breakpoint 1, main () at test.c:6
6	    printf("start test\n");
(gdb) watch a
Hardware watchpoint 2: a
(gdb) hbreak 11
Hardware assisted breakpoint 3 at 0x120000700: file test.c, line 11.
(gdb) c
Continuing.

Hardware watchpoint 2: a

Old value = 0
New value = 1
main () at test.c:8
8	    printf("a = %d\n", a);
(gdb) c
Continuing.

Hardware watchpoint 2: a

Old value = 1
New value = 2
main () at test.c:10
10	    printf("a = %d\n", a);
(gdb) c
Continuing.

Breakpoint 3, main () at test.c:11
11	    b = 2;
(gdb) c
Continuing.
[Inferior 1 (process 696656) exited normally]

Output on the target machine:

Process ./test created; pid = 696708
Listening on port 1234
Remote debugging from host 192.168.1.200, port 60742
start test
a = 1
a = 2
b = 2

Child exited with status 0

Signed-off-by: Hui Li <lihui@loongson.cn>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
This commit is contained in:
Hui Li 2025-01-06 18:21:23 +08:00 committed by Tiezhu Yang
parent 493975edcb
commit b40a956657
2 changed files with 267 additions and 0 deletions

View File

@ -142,6 +142,9 @@ case "${gdbserver_host}" in
;;
loongarch*-*-linux*) srv_tgtobj="arch/loongarch.o linux-loongarch-low.o"
srv_tgtobj="${srv_tgtobj} ${srv_linux_obj}"
srv_tgtobj="$srv_tgtobj nat/loongarch-hw-point.o"
srv_tgtobj="$srv_tgtobj nat/loongarch-linux.o"
srv_tgtobj="$srv_tgtobj nat/loongarch-linux-hw-point.o"
srv_linux_regsets=yes
srv_linux_usrregs=yes
srv_linux_thread_db=yes

View File

@ -18,6 +18,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "linux-low.h"
#include "nat/loongarch-hw-point.h"
#include "nat/loongarch-linux.h"
#include "nat/loongarch-linux-hw-point.h"
#include "tdesc.h"
#include "elf/common.h"
#include "arch/loongarch.h"
@ -34,6 +37,8 @@ public:
const gdb_byte *sw_breakpoint_from_kind (int kind, int *size) override;
bool supports_z_point_type (char z_type) override;
protected:
void low_arch_setup () override;
@ -51,6 +56,28 @@ protected:
void low_set_pc (regcache *regcache, CORE_ADDR newpc) override;
bool low_breakpoint_at (CORE_ADDR pc) override;
int low_insert_point (raw_bkpt_type type, CORE_ADDR addr,
int size, raw_breakpoint *bp) override;
int low_remove_point (raw_bkpt_type type, CORE_ADDR addr,
int size, raw_breakpoint *bp) override;
bool low_stopped_by_watchpoint () override;
CORE_ADDR low_stopped_data_address () override;
arch_process_info *low_new_process () override;
void low_delete_process (arch_process_info *info) override;
void low_new_thread (lwp_info *) override;
void low_delete_thread (arch_lwp_info *) override;
void low_new_fork (process_info *parent, process_info *child) override;
void low_prepare_to_resume (lwp_info *lwp) override;
};
/* The singleton target ops object. */
@ -71,6 +98,19 @@ loongarch_target::low_cannot_store_register (int regno)
"is not implemented by the target");
}
void
loongarch_target::low_prepare_to_resume (lwp_info *lwp)
{
loongarch_linux_prepare_to_resume (lwp);
}
/* Per-process arch-specific data we want to keep. */
struct arch_process_info
{
struct loongarch_debug_reg_state debug_reg_state;
};
/* Implementation of linux target ops method "low_arch_setup". */
void
@ -89,6 +129,7 @@ loongarch_target::low_arch_setup ()
gdb_assert (!tdesc->expedite_regs.empty ());
}
current_process ()->tdesc = tdesc.release ();
loongarch_linux_get_debug_reg_capacity (current_thread->id.lwp ());
}
/* Collect GPRs from REGCACHE into BUF. */
@ -381,6 +422,229 @@ loongarch_target::low_breakpoint_at (CORE_ADDR pc)
return false;
}
static void
loongarch_init_debug_reg_state (struct loongarch_debug_reg_state *state)
{
int i;
for (i = 0; i < LOONGARCH_HBP_MAX_NUM; ++i)
{
state->dr_addr_bp[i] = 0;
state->dr_ctrl_bp[i] = 0;
state->dr_ref_count_bp[i] = 0;
}
for (i = 0; i < LOONGARCH_HWP_MAX_NUM; ++i)
{
state->dr_addr_wp[i] = 0;
state->dr_ctrl_wp[i] = 0;
state->dr_ref_count_wp[i] = 0;
}
}
/* See nat/loongarch-linux-hw-point.h. */
struct loongarch_debug_reg_state *
loongarch_get_debug_reg_state (pid_t pid)
{
struct process_info *proc = find_process_pid (pid);
return &proc->priv->arch_private->debug_reg_state;
}
/* Implementation of target ops method "supports_z_point_type". */
bool
loongarch_target::supports_z_point_type (char z_type)
{
switch (z_type)
{
case Z_PACKET_SW_BP:
case Z_PACKET_HW_BP:
case Z_PACKET_WRITE_WP:
case Z_PACKET_READ_WP:
case Z_PACKET_ACCESS_WP:
return true;
default:
return false;
}
}
/* Implementation of linux target ops method "low_insert_point".
It actually only records the info of the to-be-inserted bp/wp;
the actual insertion will happen when threads are resumed. */
int
loongarch_target::low_insert_point (raw_bkpt_type type, CORE_ADDR addr,
int len, raw_breakpoint *bp)
{
int ret;
enum target_hw_bp_type targ_type;
struct loongarch_debug_reg_state *state
= loongarch_get_debug_reg_state (current_thread->id.pid ());
if (show_debug_regs)
fprintf (stderr, "insert_point on entry (addr=0x%08lx, len=%d)\n",
(unsigned long) addr, len);
/* Determine the type from the raw breakpoint type. */
targ_type = raw_bkpt_type_to_target_hw_bp_type (type);
if (targ_type != hw_execute)
{
if (loongarch_region_ok_for_watchpoint (addr, len))
ret = loongarch_handle_watchpoint (targ_type, addr, len,
1 /* is_insert */,
current_lwp_ptid (), state);
else
ret = -1;
}
else
{
ret = loongarch_handle_breakpoint (targ_type, addr, len,
1 /* is_insert */, current_lwp_ptid (),
state);
}
if (show_debug_regs)
loongarch_show_debug_reg_state (state, "insert_point", addr, len,
targ_type);
return ret;
}
/* Implementation of linux target ops method "low_remove_point".
It actually only records the info of the to-be-removed bp/wp,
the actual removal will be done when threads are resumed. */
int
loongarch_target::low_remove_point (raw_bkpt_type type, CORE_ADDR addr,
int len, raw_breakpoint *bp)
{
int ret;
enum target_hw_bp_type targ_type;
struct loongarch_debug_reg_state *state
= loongarch_get_debug_reg_state (current_thread->id.pid ());
if (show_debug_regs)
fprintf (stderr, "remove_point on entry (addr=0x%08lx, len=%d)\n",
(unsigned long) addr, len);
/* Determine the type from the raw breakpoint type. */
targ_type = raw_bkpt_type_to_target_hw_bp_type (type);
/* Set up state pointers. */
if (targ_type != hw_execute)
ret =
loongarch_handle_watchpoint (targ_type, addr, len, 0 /* is_insert */,
current_lwp_ptid (), state);
else
{
ret = loongarch_handle_breakpoint (targ_type, addr, len,
0 /* is_insert */, current_lwp_ptid (),
state);
}
if (show_debug_regs)
loongarch_show_debug_reg_state (state, "remove_point", addr, len,
targ_type);
return ret;
}
/* Implementation of linux target ops method "low_stopped_data_address". */
CORE_ADDR
loongarch_target::low_stopped_data_address ()
{
siginfo_t siginfo;
struct loongarch_debug_reg_state *state;
int pid = current_thread->id.lwp ();
/* Get the siginfo. */
if (ptrace (PTRACE_GETSIGINFO, pid, NULL, &siginfo) != 0)
return (CORE_ADDR) 0;
/* Need to be a hardware breakpoint/watchpoint trap. */
if (siginfo.si_signo != SIGTRAP
|| (siginfo.si_code & 0xffff) != 0x0004 /* TRAP_HWBKPT */)
return (CORE_ADDR) 0;
/* Check if the address matches any watched address. */
state = loongarch_get_debug_reg_state (current_thread->id.pid ());
CORE_ADDR result;
if (loongarch_stopped_data_address (state, (CORE_ADDR) siginfo.si_addr, &result))
return result;
return (CORE_ADDR) 0;
}
/* Implementation of linux target ops method "low_stopped_by_watchpoint". */
bool
loongarch_target::low_stopped_by_watchpoint ()
{
return (low_stopped_data_address () != 0);
}
/* Implementation of linux target ops method "low_new_process". */
arch_process_info *
loongarch_target::low_new_process ()
{
struct arch_process_info *info = XCNEW (struct arch_process_info);
loongarch_init_debug_reg_state (&info->debug_reg_state);
return info;
}
/* Implementation of linux target ops method "low_delete_process". */
void
loongarch_target::low_delete_process (arch_process_info *info)
{
xfree (info);
}
void
loongarch_target::low_new_thread (lwp_info *lwp)
{
loongarch_linux_new_thread (lwp);
}
void
loongarch_target::low_delete_thread (arch_lwp_info *arch_lwp)
{
loongarch_linux_delete_thread (arch_lwp);
}
/* Implementation of linux target ops method "low_new_fork". */
void
loongarch_target::low_new_fork (process_info *parent,
process_info *child)
{
/* These are allocated by linux_add_process. */
gdb_assert (parent->priv != NULL
&& parent->priv->arch_private != NULL);
gdb_assert (child->priv != NULL
&& child->priv->arch_private != NULL);
/* 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. The debug registers mirror will become zeroed
in the end before detaching the forked off process, thus making
this compatible with older Linux kernels too. */
*child->priv->arch_private = *parent->priv->arch_private;
}
/* The linux target ops object. */
linux_process_target *the_linux_target = &the_loongarch_target;