[PATCH 3/3] MIPS h/w watchpoint in GDBserver

Yao Qi
2013-05-30T02:44:06Z

Hi,
This patch adds the mips hardware watchpoint support in GDBserver.

gdb/gdbserver:

2013-05-30 Jie Zhang <jie@codesourcery.com>
Daniel Jacobowitz <dan@codesourcery.com>
Yao Qi <yao@codesourcery.com>

* Makefile.in (SFILES): Add common/mips-linux-watch.c.
(mips-linux-watch.o): New rule.
* configure.srv (srv_tgtobj): Add mips-linux-watch.o
* linux-mips-low.c: Include mips-linux-watch.h.
(struct arch_process_info, struct arch_lwp_info): New.
(update_debug_registers_callback): New.
(mips_linux_new_process, mips_linux_new_thread) New.
(mips_linux_prepare_to_resume, mips_insert_point): New.
(mips_remove_point, mips_stopped_by_watchpoint): New.
(mips_stopped_data_address): New.
(the_low_target): Add watchpoint support functions.
---
gdb/gdbserver/Makefile.in | 5 +-
gdb/gdbserver/configure.srv | 2 +-
gdb/gdbserver/linux-mips-low.c | 341 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 346 insertions(+), 2 deletions(-)

diff --git a/gdb/gdbserver/Makefile.in b/gdb/gdbserver/Makefile.in
index 9b1771f..f105e28 100644
--- a/gdb/gdbserver/Makefile.in
+++ b/gdb/gdbserver/Makefile.in
@@ -157,7 +157,7 @@ SFILES= $(srcdir)/gdbreplay.c $(srcdir)/inferiors.c $(srcdir)/dll.c \
$(srcdir)/common/common-utils.c $(srcdir)/common/xml-utils.c \
$(srcdir)/common/linux-osdata.c $(srcdir)/common/ptid.c \
$(srcdir)/common/buffer.c $(srcdir)/common/linux-btrace.c \
- $(srcdir)/common/filestuff.c
+ $(srcdir)/common/filestuff.c $(srcdir)/common/mips-linux-watch.c

DEPFILES = @GDBSERVER_DEPFILES@

@@ -553,6 +553,9 @@ agent.o: ../common/agent.c
linux-btrace.o: ../common/linux-btrace.c $(linux_btrace_h) $(server_h)
$(CC) -c $(CPPFLAGS) $(INTERNAL_CFLAGS) $<

+mips-linux-watch.o: ../common/mips-linux-watch.c ../common/mips-linux-watch.h $(server_h)
+ $(CC) -c $(CPPFLAGS) $(INTERNAL_CFLAGS) $<
+
# We build vasprintf with -DHAVE_CONFIG_H because we want that unit to
# include our config.h file. Otherwise, some system headers do not get
# included, and the compiler emits a warning about implicitly defined
diff --git a/gdb/gdbserver/configure.srv b/gdb/gdbserver/configure.srv
index 9344b17..b024dd3 100644
--- a/gdb/gdbserver/configure.srv
+++ b/gdb/gdbserver/configure.srv
@@ -184,7 +184,7 @@ case "${target}" in
srv_regobj="${srv_regobj} mips64-linux.o"
srv_regobj="${srv_regobj} mips64-dsp-linux.o"
srv_tgtobj="linux-low.o linux-osdata.o linux-mips-low.o linux-procfs.o"
- srv_tgtobj="${srv_tgtobj} linux-ptrace.o"
+ srv_tgtobj="${srv_tgtobj} linux-ptrace.o mips-linux-watch.o"
srv_xmlfiles="mips-linux.xml"
srv_xmlfiles="${srv_xmlfiles} mips-dsp-linux.xml"
srv_xmlfiles="${srv_xmlfiles} mips-cpu.xml"
diff --git a/gdb/gdbserver/linux-mips-low.c b/gdb/gdbserver/linux-mips-low.c
index 0cf83be..1eb83eb 100644
--- a/gdb/gdbserver/linux-mips-low.c
+++ b/gdb/gdbserver/linux-mips-low.c
@@ -20,6 +20,7 @@
#include "linux-low.h"

#include <sys/ptrace.h>
+#include "mips-linux-watch.h"
#include <endian.h>

#include "gdb_proc_service.h"
@@ -145,6 +146,39 @@ mips_arch_setup (void)
init_registers ();
}

+/* Per-process arch-specific data we want to keep. */
+
+struct arch_process_info
+{
+ /* -1 if the kernel and/or CPU do not support watch registers.
+ 1 if watch_readback is valid and we can read style, num_valid
+ and the masks.
+ 0 if we need to read the watch_readback. */
+
+ int watch_readback_valid;
+
+ /* Cached watch register read values. */
+
+ struct pt_watch_regs watch_readback;
+
+ /* Current watchpoint requests for this process. */
+
+ struct mips_watchpoint *current_watches;
+
+ /* The current set of watch register values for writing the
+ registers. */
+
+ struct pt_watch_regs watch_mirror;
+};
+
+/* Per-thread arch-specific data we want to keep. */
+
+struct arch_lwp_info
+{
+ /* Non-zero if our copy differs from what's recorded in the thread. */
+ int debug_registers_changed;
+};
+
/* From mips-linux-nat.c. */

/* Pseudo registers can not be read. ptrace does not provide a way to
@@ -235,6 +269,303 @@ mips_breakpoint_at (CORE_ADDR where)
return 0;
}

+/* Mark the debug registers of lwp, represented by ENTRY, is changed,
+ if the lwp's process id is *PID_P. */
+
+static int
+update_debug_registers_callback (struct inferior_list_entry *entry,
+ void *pid_p)
+{
+ struct lwp_info *lwp = (struct lwp_info *) entry;
+ int pid = *(int *) pid_p;
+
+ /* Only update the threads of this process. */
+ if (pid_of (lwp) == pid)
+ {
+ /* The actual update is done later just before resuming the lwp,
+ we just mark that the registers need updating. */
+ lwp->arch_private->debug_registers_changed = 1;
+
+ /* If the lwp isn't stopped, force it to momentarily pause, so
+ we can update its debug registers. */
+ if (!lwp->stopped)
+ linux_stop_lwp (lwp);
+ }
+
+ return 0;
+}
+
+/* This is the implementation of linux_target_ops method
+ new_process. */
+
+static struct arch_process_info *
+mips_linux_new_process (void)
+{
+ struct arch_process_info *info = xcalloc (1, sizeof (*info));
+
+ return info;
+}
+
+/* This is the implementation of linux_target_ops method new_thread.
+ Mark the debug registers are changed, so the threads' copies will
+ be updated. */
+
+static struct arch_lwp_info *
+mips_linux_new_thread (void)
+{
+ struct arch_lwp_info *info = xcalloc (1, sizeof (*info));
+
+ info->debug_registers_changed = 1;
+
+ return info;
+}
+
+/* This is the implementation of linux_target_ops method
+ prepare_to_resume. If the debug regs have changed, update the
+ thread's copies. */
+
+static void
+mips_linux_prepare_to_resume (struct lwp_info *lwp)
+{
+ ptid_t ptid = ptid_of (lwp);
+ struct process_info *proc = find_process_pid (ptid_get_pid (ptid));
+ struct arch_process_info *private = proc->private->arch_private;
+
+ if (lwp->arch_private->debug_registers_changed)
+ {
+ /* Only update the debug registers if we have set or unset a
+ watchpoint already. */
+ if (mips_linux_watch_get_num_valid (&private->watch_mirror) > 0)
+ {
+ /* Write the mirrored watch register values. */
+ int tid = ptid_get_lwp (ptid);
+
+ if (ptrace (PTRACE_SET_WATCH_REGS, tid, &private->watch_mirror) == -1)
+ perror_with_name ("Couldn't write debug register");
+ }
+
+ lwp->arch_private->debug_registers_changed = 0;
+ }
+}
+
+/* This is the implementation of linux_target_ops method
+ insert_point. */
+
+static int
+mips_insert_point (char type, CORE_ADDR addr, int len)
+{
+ struct process_info *proc = current_process ();
+ struct arch_process_info *private = proc->private->arch_private;
+ struct pt_watch_regs regs;
+ struct mips_watchpoint *new_watch;
+ struct mips_watchpoint **pw;
+ int pid;
+ long lwpid;
+
+ /* Breakpoint/watchpoint types:
+ '0' - software-breakpoint (not supported)
+ '1' - hardware-breakpoint (not supported)
+ '2' - write watchpoint (supported)
+ '3' - read watchpoint (supported)
+ '4' - access watchpoint (supported). */
+
+ if (type < '2' || type > '4')
+ {
+ /* Unsupported. */
+ return 1;
+ }
+
+ lwpid = lwpid_of (get_thread_lwp (current_inferior));
+ if (!mips_linux_read_watch_registers (lwpid,
+ &private->watch_readback,
+ &private->watch_readback_valid,
+ 0))
+ return -1;
+
+ if (len <= 0)
+ return -1;
+
+ regs = private->watch_readback;
+ /* Add the current watches. */
+ mips_linux_watch_populate_regs (private->current_watches, &regs);
+
+ /* Now try to add the new watch. */
+ if (!mips_linux_watch_try_one_watch (&regs, addr, len,
+ mips_linux_watch_type_to_irw (type)))
+ return -1;
+
+ /* It fit. Stick it on the end of the list. */
+ new_watch = (struct mips_watchpoint *)
+ xmalloc (sizeof (struct mips_watchpoint));
+ new_watch->addr = addr;
+ new_watch->len = len;
+ new_watch->type = type;
+ new_watch->next = NULL;
+
+ pw = &private->current_watches;
+ while (*pw != NULL)
+ pw = &(*pw)->next;
+ *pw = new_watch;
+
+ private->watch_mirror = regs;
+
+ /* Only update the threads of this process. */
+ pid = pid_of (proc);
+ find_inferior (&all_lwps, update_debug_registers_callback, &pid);
+
+ return 0;
+}
+
+/* This is the implementation of linux_target_ops method
+ remove_point. */
+
+static int
+mips_remove_point (char type, CORE_ADDR addr, int len)
+{
+ struct process_info *proc = current_process ();
+ struct arch_process_info *private = proc->private->arch_private;
+
+ int deleted_one;
+ int pid;
+
+ struct mips_watchpoint **pw;
+ struct mips_watchpoint *w;
+
+ /* Breakpoint/watchpoint types:
+ '0' - software-breakpoint (not supported)
+ '1' - hardware-breakpoint (not supported)
+ '2' - write watchpoint (supported)
+ '3' - read watchpoint (supported)
+ '4' - access watchpoint (supported). */
+
+ if (type < '2' || type > '4')
+ {
+ /* Unsupported. */
+ return 1;
+ }
+
+ /* Search for a known watch that matches. Then unlink and free
+ it. */
+ deleted_one = 0;
+ pw = &private->current_watches;
+ while ((w = *pw))
+ {
+ if (w->addr == addr && w->len == len && w->type == type)
+ {
+ *pw = w->next;
+ free (w);
+ deleted_one = 1;
+ break;
+ }
+ pw = &(w->next);
+ }
+
+ if (!deleted_one)
+ return -1; /* We don't know about it, fail doing nothing. */
+
+ /* At this point watch_readback is known to be valid because we
+ could not have added the watch without reading it. */
+ gdb_assert (private->watch_readback_valid == 1);
+
+ private->watch_mirror = private->watch_readback;
+ mips_linux_watch_populate_regs (private->current_watches,
+ &private->watch_mirror);
+
+ /* Only update the threads of this process. */
+ pid = pid_of (proc);
+ find_inferior (&all_lwps, update_debug_registers_callback, &pid);
+ return 0;
+}
+
+/* This is the implementation of linux_target_ops method
+ stopped_by_watchpoint. The watchhi R and W bits indicate
+ the watch register triggered. */
+
+static int
+mips_stopped_by_watchpoint (void)
+{
+ struct process_info *proc = current_process ();
+ struct arch_process_info *private = proc->private->arch_private;
+ int n;
+ int num_valid;
+ long lwpid = lwpid_of (get_thread_lwp (current_inferior));
+
+ if (!mips_linux_read_watch_registers (lwpid,
+ &private->watch_readback,
+ &private->watch_readback_valid,
+ 1))
+ return 0;
+
+ num_valid = mips_linux_watch_get_num_valid (&private->watch_readback);
+
+ for (n = 0; n < MAX_DEBUG_REGISTER && n < num_valid; n++)
+ if (mips_linux_watch_get_watchhi (&private->watch_readback, n)
+ & (R_MASK | W_MASK))
+ return 1;
+
+ return 0;
+}
+
+/* This is the implementation of linux_target_ops method
+ stopped_data_address. */
+
+static CORE_ADDR
+mips_stopped_data_address (void)
+{
+ struct process_info *proc = current_process ();
+ struct arch_process_info *private = proc->private->arch_private;
+ int n;
+ int num_valid;
+ long lwpid = lwpid_of (get_thread_lwp (current_inferior));
+
+ /* On mips we don't know the low order 3 bits of the data address.
+ GDB does not support remote targets that can't report the
+ watchpoint address. So, make our best guess; return the starting
+ address of a watchpoint request which overlaps the one that
+ triggered. */
+
+ if (!mips_linux_read_watch_registers (lwpid,
+ &private->watch_readback,
+ &private->watch_readback_valid,
+ 0))
+ return 0;
+
+ num_valid = mips_linux_watch_get_num_valid (&private->watch_readback);
+
+ for (n = 0; n < MAX_DEBUG_REGISTER && n < num_valid; n++)
+ if (mips_linux_watch_get_watchhi (&private->watch_readback, n)
+ & (R_MASK | W_MASK))
+ {
+ CORE_ADDR t_low, t_hi;
+ int t_irw;
+ struct mips_watchpoint *watch;
+
+ t_low = mips_linux_watch_get_watchlo (&private->watch_readback, n);
+ t_irw = t_low & IRW_MASK;
+ t_hi = (mips_linux_watch_get_watchhi (&private->watch_readback, n)
+ | IRW_MASK);
+ t_low &= ~(CORE_ADDR)t_hi;
+
+ for (watch = private->current_watches;
+ watch != NULL;
+ watch = watch->next)
+ {
+ CORE_ADDR addr = watch->addr;
+ CORE_ADDR last_byte = addr + watch->len - 1;
+ if ((t_irw & mips_linux_watch_type_to_irw (watch->type))
+ == 0)
+ /* Different type. */
+ continue;
+ /* Check for overlap of even a single byte. */
+ if (last_byte >= t_low && addr <= t_low + t_hi)
+ return addr;
+ }
+ }
+
+ /* Shouldn't happen. */
+ return 0;
+}
+
/* Fetch the thread-local storage pointer for libthread_db. */

ps_err_e
@@ -437,4 +768,14 @@ struct linux_target_ops the_low_target = {
mips_reinsert_addr,
0,
mips_breakpoint_at,
+ mips_insert_point,
+ mips_remove_point,
+ mips_stopped_by_watchpoint,
+ mips_stopped_data_address,
+ NULL,
+ NULL,
+ NULL, /* siginfo_fixup */
+ mips_linux_new_process,
+ mips_linux_new_thread,
+ mips_linux_prepare_to_resume
};
--
1.7.7.6