Conditional Z1 breakpoint hangs GDBserver.
[deliverable/binutils-gdb.git] / gdb / gdbserver / mem-break.c
index 5cdb3d9ce667258821fc0021627cd31db30ef793..5df950d34809148c042a8f6bd1dbe937c3aef4d9 100644 (file)
@@ -1,5 +1,5 @@
 /* Memory breakpoint operations for the remote server for GDB.
-   Copyright (C) 2002, 2003, 2005, 2007 Free Software Foundation, Inc.
+   Copyright (C) 2002-2014 Free Software Foundation, Inc.
 
    Contributed by MontaVista Software.
 
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "server.h"
+#include "regcache.h"
+#include "ax.h"
+#include <stdint.h>
 
 const unsigned char *breakpoint_data;
 int breakpoint_len;
 
 #define MAX_BREAKPOINT_LEN 8
 
-struct breakpoint
+/* GDB will never try to install multiple breakpoints at the same
+   address.  But, we need to keep track of internal breakpoints too,
+   and so we do need to be able to install multiple breakpoints at the
+   same address transparently.  We keep track of two different, and
+   closely related structures.  A raw breakpoint, which manages the
+   low level, close to the metal aspect of a breakpoint.  It holds the
+   breakpoint address, and a buffer holding a copy of the instructions
+   that would be in memory had not been a breakpoint there (we call
+   that the shadow memory of the breakpoint).  We occasionally need to
+   temporarilly uninsert a breakpoint without the client knowing about
+   it (e.g., to step over an internal breakpoint), so we keep an
+   `inserted' state associated with this low level breakpoint
+   structure.  There can only be one such object for a given address.
+   Then, we have (a bit higher level) breakpoints.  This structure
+   holds a callback to be called whenever a breakpoint is hit, a
+   high-level type, and a link to a low level raw breakpoint.  There
+   can be many high-level breakpoints at the same address, and all of
+   them will point to the same raw breakpoint, which is reference
+   counted.  */
+
+/* The low level, physical, raw breakpoint.  */
+struct raw_breakpoint
 {
-  struct breakpoint *next;
+  struct raw_breakpoint *next;
+
+  /* A reference count.  Each high level breakpoint referencing this
+     raw breakpoint accounts for one reference.  */
+  int refcount;
+
+  /* The breakpoint's insertion address.  There can only be one raw
+     breakpoint for a given PC.  */
   CORE_ADDR pc;
+
+  /* The breakpoint's shadow memory.  */
   unsigned char old_data[MAX_BREAKPOINT_LEN];
 
-  /* Non-zero iff we are stepping over this breakpoint.  */
-  int reinserting;
+  /* Non-zero if this breakpoint is currently inserted in the
+     inferior.  */
+  int inserted;
+
+  /* Non-zero if this breakpoint is currently disabled because we no
+     longer detect it as inserted.  */
+  int shlib_disabled;
+};
 
-  /* Non-NULL iff this breakpoint was inserted to step over
-     another one.  Points to the other breakpoint (which is also
-     in the *next chain somewhere).  */
-  struct breakpoint *breakpoint_to_reinsert;
+/* The type of a breakpoint.  */
+enum bkpt_type
+  {
+    /* A GDB breakpoint, requested with a Z0 packet.  */
+    gdb_breakpoint,
 
-  /* Function to call when we hit this breakpoint.  */
-  void (*handler) (CORE_ADDR);
+    /* A basic-software-single-step breakpoint.  */
+    reinsert_breakpoint,
+
+    /* Any other breakpoint type that doesn't require specific
+       treatment goes here.  E.g., an event breakpoint.  */
+    other_breakpoint,
+  };
+
+struct point_cond_list
+{
+  /* Pointer to the agent expression that is the breakpoint's
+     conditional.  */
+  struct agent_expr *cond;
+
+  /* Pointer to the next condition.  */
+  struct point_cond_list *next;
 };
 
-struct breakpoint *breakpoints;
+struct point_command_list
+{
+  /* Pointer to the agent expression that is the breakpoint's
+     commands.  */
+  struct agent_expr *cmd;
 
-void
-set_breakpoint_at (CORE_ADDR where, void (*handler) (CORE_ADDR))
+  /* Flag that is true if this command should run even while GDB is
+     disconnected.  */
+  int persistence;
+
+  /* Pointer to the next command.  */
+  struct point_command_list *next;
+};
+
+/* A high level (in gdbserver's perspective) breakpoint.  */
+struct breakpoint
 {
+  struct breakpoint *next;
+
+  /* The breakpoint's type.  */
+  enum bkpt_type type;
+
+  /* Pointer to the condition list that should be evaluated on
+     the target or NULL if the breakpoint is unconditional or
+     if GDB doesn't want us to evaluate the conditionals on the
+     target's side.  */
+  struct point_cond_list *cond_list;
+
+  /* Point to the list of commands to run when this is hit.  */
+  struct point_command_list *command_list;
+
+  /* Link to this breakpoint's raw breakpoint.  This is always
+     non-NULL.  */
+  struct raw_breakpoint *raw;
+
+  /* Function to call when we hit this breakpoint.  If it returns 1,
+     the breakpoint shall be deleted; 0 or if this callback is NULL,
+     it will be left inserted.  */
+  int (*handler) (CORE_ADDR);
+};
+
+int
+any_persistent_commands ()
+{
+  struct process_info *proc = current_process ();
   struct breakpoint *bp;
+  struct point_command_list *cl;
+
+  for (bp = proc->breakpoints; bp != NULL; bp = bp->next)
+    {
+      for (cl = bp->command_list; cl != NULL; cl = cl->next)
+       if (cl->persistence)
+         return 1;
+    }
+
+  return 0;
+}
+
+static struct raw_breakpoint *
+find_raw_breakpoint_at (CORE_ADDR where)
+{
+  struct process_info *proc = current_process ();
+  struct raw_breakpoint *bp;
+
+  for (bp = proc->raw_breakpoints; bp != NULL; bp = bp->next)
+    if (bp->pc == where)
+      return bp;
+
+  return NULL;
+}
+
+static struct raw_breakpoint *
+set_raw_breakpoint_at (CORE_ADDR where)
+{
+  struct process_info *proc = current_process ();
+  struct raw_breakpoint *bp;
+  int err;
+  unsigned char buf[MAX_BREAKPOINT_LEN];
 
   if (breakpoint_data == NULL)
     error ("Target does not support breakpoints.");
 
-  bp = malloc (sizeof (struct breakpoint));
-  memset (bp, 0, sizeof (struct breakpoint));
-
-  (*the_target->read_memory) (where, bp->old_data,
-                             breakpoint_len);
-  (*the_target->write_memory) (where, breakpoint_data,
-                              breakpoint_len);
+  bp = find_raw_breakpoint_at (where);
+  if (bp != NULL)
+    {
+      bp->refcount++;
+      return bp;
+    }
 
+  bp = xcalloc (1, sizeof (*bp));
   bp->pc = where;
-  bp->handler = handler;
+  bp->refcount = 1;
+
+  /* Note that there can be fast tracepoint jumps installed in the
+     same memory range, so to get at the original memory, we need to
+     use read_inferior_memory, which masks those out.  */
+  err = read_inferior_memory (where, buf, breakpoint_len);
+  if (err != 0)
+    {
+      if (debug_threads)
+       debug_printf ("Failed to read shadow memory of"
+                     " breakpoint at 0x%s (%s).\n",
+                     paddress (where), strerror (err));
+      free (bp);
+      return NULL;
+    }
+  memcpy (bp->old_data, buf, breakpoint_len);
+
+  err = (*the_target->write_memory) (where, breakpoint_data,
+                                    breakpoint_len);
+  if (err != 0)
+    {
+      if (debug_threads)
+       debug_printf ("Failed to insert breakpoint at 0x%s (%s).\n",
+                     paddress (where), strerror (err));
+      free (bp);
+      return NULL;
+    }
 
-  bp->next = breakpoints;
-  breakpoints = bp;
+  /* Link the breakpoint in.  */
+  bp->inserted = 1;
+  bp->next = proc->raw_breakpoints;
+  proc->raw_breakpoints = bp;
+  return bp;
 }
 
-static void
-delete_breakpoint (struct breakpoint *bp)
+/* Notice that breakpoint traps are always installed on top of fast
+   tracepoint jumps.  This is even if the fast tracepoint is installed
+   at a later time compared to when the breakpoint was installed.
+   This means that a stopping breakpoint or tracepoint has higher
+   "priority".  In turn, this allows having fast and slow tracepoints
+   (and breakpoints) at the same address behave correctly.  */
+
+
+/* A fast tracepoint jump.  */
+
+struct fast_tracepoint_jump
+{
+  struct fast_tracepoint_jump *next;
+
+  /* A reference count.  GDB can install more than one fast tracepoint
+     at the same address (each with its own action list, for
+     example).  */
+  int refcount;
+
+  /* The fast tracepoint's insertion address.  There can only be one
+     of these for a given PC.  */
+  CORE_ADDR pc;
+
+  /* Non-zero if this fast tracepoint jump is currently inserted in
+     the inferior.  */
+  int inserted;
+
+  /* The length of the jump instruction.  */
+  int length;
+
+  /* A poor-man's flexible array member, holding both the jump
+     instruction to insert, and a copy of the instruction that would
+     be in memory had not been a jump there (the shadow memory of the
+     tracepoint jump).  */
+  unsigned char insn_and_shadow[0];
+};
+
+/* Fast tracepoint FP's jump instruction to insert.  */
+#define fast_tracepoint_jump_insn(fp) \
+  ((fp)->insn_and_shadow + 0)
+
+/* The shadow memory of fast tracepoint jump FP.  */
+#define fast_tracepoint_jump_shadow(fp) \
+  ((fp)->insn_and_shadow + (fp)->length)
+
+
+/* Return the fast tracepoint jump set at WHERE.  */
+
+static struct fast_tracepoint_jump *
+find_fast_tracepoint_jump_at (CORE_ADDR where)
+{
+  struct process_info *proc = current_process ();
+  struct fast_tracepoint_jump *jp;
+
+  for (jp = proc->fast_tracepoint_jumps; jp != NULL; jp = jp->next)
+    if (jp->pc == where)
+      return jp;
+
+  return NULL;
+}
+
+int
+fast_tracepoint_jump_here (CORE_ADDR where)
 {
-  struct breakpoint *cur;
+  struct fast_tracepoint_jump *jp = find_fast_tracepoint_jump_at (where);
 
-  if (breakpoints == bp)
+  return (jp != NULL);
+}
+
+int
+delete_fast_tracepoint_jump (struct fast_tracepoint_jump *todel)
+{
+  struct fast_tracepoint_jump *bp, **bp_link;
+  int ret;
+  struct process_info *proc = current_process ();
+
+  bp = proc->fast_tracepoint_jumps;
+  bp_link = &proc->fast_tracepoint_jumps;
+
+  while (bp)
     {
-      breakpoints = bp->next;
-      (*the_target->write_memory) (bp->pc, bp->old_data,
-                                  breakpoint_len);
-      free (bp);
+      if (bp == todel)
+       {
+         if (--bp->refcount == 0)
+           {
+             struct fast_tracepoint_jump *prev_bp_link = *bp_link;
+             unsigned char *buf;
+
+             /* Unlink it.  */
+             *bp_link = bp->next;
+
+             /* Since there can be breakpoints inserted in the same
+                address range, we use `write_inferior_memory', which
+                takes care of layering breakpoints on top of fast
+                tracepoints, and on top of the buffer we pass it.
+                This works because we've already unlinked the fast
+                tracepoint jump above.  Also note that we need to
+                pass the current shadow contents, because
+                write_inferior_memory updates any shadow memory with
+                what we pass here, and we want that to be a nop.  */
+             buf = alloca (bp->length);
+             memcpy (buf, fast_tracepoint_jump_shadow (bp), bp->length);
+             ret = write_inferior_memory (bp->pc, buf, bp->length);
+             if (ret != 0)
+               {
+                 /* Something went wrong, relink the jump.  */
+                 *bp_link = prev_bp_link;
+
+                 if (debug_threads)
+                   debug_printf ("Failed to uninsert fast tracepoint jump "
+                                 "at 0x%s (%s) while deleting it.\n",
+                                 paddress (bp->pc), strerror (ret));
+                 return ret;
+               }
+
+             free (bp);
+           }
+
+         return 0;
+       }
+      else
+       {
+         bp_link = &bp->next;
+         bp = *bp_link;
+       }
+    }
+
+  warning ("Could not find fast tracepoint jump in list.");
+  return ENOENT;
+}
+
+void
+inc_ref_fast_tracepoint_jump (struct fast_tracepoint_jump *jp)
+{
+  jp->refcount++;
+}
+
+struct fast_tracepoint_jump *
+set_fast_tracepoint_jump (CORE_ADDR where,
+                         unsigned char *insn, ULONGEST length)
+{
+  struct process_info *proc = current_process ();
+  struct fast_tracepoint_jump *jp;
+  int err;
+  unsigned char *buf;
+
+  /* We refcount fast tracepoint jumps.  Check if we already know
+     about a jump at this address.  */
+  jp = find_fast_tracepoint_jump_at (where);
+  if (jp != NULL)
+    {
+      jp->refcount++;
+      return jp;
+    }
+
+  /* We don't, so create a new object.  Double the length, because the
+     flexible array member holds both the jump insn, and the
+     shadow.  */
+  jp = xcalloc (1, sizeof (*jp) + (length * 2));
+  jp->pc = where;
+  jp->length = length;
+  memcpy (fast_tracepoint_jump_insn (jp), insn, length);
+  jp->refcount = 1;
+  buf = alloca (length);
+
+  /* Note that there can be trap breakpoints inserted in the same
+     address range.  To access the original memory contents, we use
+     `read_inferior_memory', which masks out breakpoints.  */
+  err = read_inferior_memory (where, buf, length);
+  if (err != 0)
+    {
+      if (debug_threads)
+       debug_printf ("Failed to read shadow memory of"
+                     " fast tracepoint at 0x%s (%s).\n",
+                     paddress (where), strerror (err));
+      free (jp);
+      return NULL;
+    }
+  memcpy (fast_tracepoint_jump_shadow (jp), buf, length);
+
+  /* Link the jump in.  */
+  jp->inserted = 1;
+  jp->next = proc->fast_tracepoint_jumps;
+  proc->fast_tracepoint_jumps = jp;
+
+  /* Since there can be trap breakpoints inserted in the same address
+     range, we use use `write_inferior_memory', which takes care of
+     layering breakpoints on top of fast tracepoints, on top of the
+     buffer we pass it.  This works because we've already linked in
+     the fast tracepoint jump above.  Also note that we need to pass
+     the current shadow contents, because write_inferior_memory
+     updates any shadow memory with what we pass here, and we want
+     that to be a nop.  */
+  err = write_inferior_memory (where, buf, length);
+  if (err != 0)
+    {
+      if (debug_threads)
+       debug_printf ("Failed to insert fast tracepoint jump at 0x%s (%s).\n",
+                     paddress (where), strerror (err));
+
+      /* Unlink it.  */
+      proc->fast_tracepoint_jumps = jp->next;
+      free (jp);
+
+      return NULL;
+    }
+
+  return jp;
+}
+
+void
+uninsert_fast_tracepoint_jumps_at (CORE_ADDR pc)
+{
+  struct fast_tracepoint_jump *jp;
+  int err;
+
+  jp = find_fast_tracepoint_jump_at (pc);
+  if (jp == NULL)
+    {
+      /* This can happen when we remove all breakpoints while handling
+        a step-over.  */
+      if (debug_threads)
+       debug_printf ("Could not find fast tracepoint jump at 0x%s "
+                     "in list (uninserting).\n",
+                     paddress (pc));
       return;
     }
-  cur = breakpoints;
-  while (cur->next)
+
+  if (jp->inserted)
     {
-      if (cur->next == bp)
+      unsigned char *buf;
+
+      jp->inserted = 0;
+
+      /* Since there can be trap breakpoints inserted in the same
+        address range, we use use `write_inferior_memory', which
+        takes care of layering breakpoints on top of fast
+        tracepoints, and on top of the buffer we pass it.  This works
+        because we've already marked the fast tracepoint fast
+        tracepoint jump uninserted above.  Also note that we need to
+        pass the current shadow contents, because
+        write_inferior_memory updates any shadow memory with what we
+        pass here, and we want that to be a nop.  */
+      buf = alloca (jp->length);
+      memcpy (buf, fast_tracepoint_jump_shadow (jp), jp->length);
+      err = write_inferior_memory (jp->pc, buf, jp->length);
+      if (err != 0)
        {
-         cur->next = bp->next;
-         (*the_target->write_memory) (bp->pc, bp->old_data,
-                                      breakpoint_len);
+         jp->inserted = 1;
+
+         if (debug_threads)
+           debug_printf ("Failed to uninsert fast tracepoint jump at"
+                         " 0x%s (%s).\n",
+                         paddress (pc), strerror (err));
+       }
+    }
+}
+
+void
+reinsert_fast_tracepoint_jumps_at (CORE_ADDR where)
+{
+  struct fast_tracepoint_jump *jp;
+  int err;
+  unsigned char *buf;
+
+  jp = find_fast_tracepoint_jump_at (where);
+  if (jp == NULL)
+    {
+      /* This can happen when we remove breakpoints when a tracepoint
+        hit causes a tracing stop, while handling a step-over.  */
+      if (debug_threads)
+       debug_printf ("Could not find fast tracepoint jump at 0x%s "
+                     "in list (reinserting).\n",
+                     paddress (where));
+      return;
+    }
+
+  if (jp->inserted)
+    error ("Jump already inserted at reinsert time.");
+
+  jp->inserted = 1;
+
+  /* Since there can be trap breakpoints inserted in the same address
+     range, we use `write_inferior_memory', which takes care of
+     layering breakpoints on top of fast tracepoints, and on top of
+     the buffer we pass it.  This works because we've already marked
+     the fast tracepoint jump inserted above.  Also note that we need
+     to pass the current shadow contents, because
+     write_inferior_memory updates any shadow memory with what we pass
+     here, and we want that to be a nop.  */
+  buf = alloca (jp->length);
+  memcpy (buf, fast_tracepoint_jump_shadow (jp), jp->length);
+  err = write_inferior_memory (where, buf, jp->length);
+  if (err != 0)
+    {
+      jp->inserted = 0;
+
+      if (debug_threads)
+       debug_printf ("Failed to reinsert fast tracepoint jump at"
+                     " 0x%s (%s).\n",
+                     paddress (where), strerror (err));
+    }
+}
+
+struct breakpoint *
+set_breakpoint_at (CORE_ADDR where, int (*handler) (CORE_ADDR))
+{
+  struct process_info *proc = current_process ();
+  struct breakpoint *bp;
+  struct raw_breakpoint *raw;
+
+  raw = set_raw_breakpoint_at (where);
+
+  if (raw == NULL)
+    {
+      /* warn? */
+      return NULL;
+    }
+
+  bp = xcalloc (1, sizeof (struct breakpoint));
+  bp->type = other_breakpoint;
+
+  bp->raw = raw;
+  bp->handler = handler;
+
+  bp->next = proc->breakpoints;
+  proc->breakpoints = bp;
+
+  return bp;
+}
+
+static int
+delete_raw_breakpoint (struct process_info *proc, struct raw_breakpoint *todel)
+{
+  struct raw_breakpoint *bp, **bp_link;
+  int ret;
+
+  bp = proc->raw_breakpoints;
+  bp_link = &proc->raw_breakpoints;
+
+  while (bp)
+    {
+      if (bp == todel)
+       {
+         if (bp->inserted)
+           {
+             struct raw_breakpoint *prev_bp_link = *bp_link;
+             unsigned char buf[MAX_BREAKPOINT_LEN];
+
+             *bp_link = bp->next;
+
+             /* Since there can be trap breakpoints inserted in the
+                same address range, we use `write_inferior_memory',
+                which takes care of layering breakpoints on top of
+                fast tracepoints, and on top of the buffer we pass
+                it.  This works because we've already unlinked the
+                fast tracepoint jump above.  Also note that we need
+                to pass the current shadow contents, because
+                write_inferior_memory updates any shadow memory with
+                what we pass here, and we want that to be a nop.  */
+             memcpy (buf, bp->old_data, breakpoint_len);
+             ret = write_inferior_memory (bp->pc, buf, breakpoint_len);
+             if (ret != 0)
+               {
+                 /* Something went wrong, relink the breakpoint.  */
+                 *bp_link = prev_bp_link;
+
+                 if (debug_threads)
+                   debug_printf ("Failed to uninsert raw breakpoint "
+                                 "at 0x%s (%s) while deleting it.\n",
+                                 paddress (bp->pc), strerror (ret));
+                 return ret;
+               }
+
+           }
+         else
+           *bp_link = bp->next;
+
          free (bp);
-         return;
+         return 0;
+       }
+      else
+       {
+         bp_link = &bp->next;
+         bp = *bp_link;
        }
     }
-  warning ("Could not find breakpoint in list.");
+
+  warning ("Could not find raw breakpoint in list.");
+  return ENOENT;
+}
+
+static int
+release_breakpoint (struct process_info *proc, struct breakpoint *bp)
+{
+  int newrefcount;
+  int ret;
+
+  newrefcount = bp->raw->refcount - 1;
+  if (newrefcount == 0)
+    {
+      ret = delete_raw_breakpoint (proc, bp->raw);
+      if (ret != 0)
+       return ret;
+    }
+  else
+    bp->raw->refcount = newrefcount;
+
+  free (bp);
+
+  return 0;
 }
 
-static struct breakpoint *
-find_breakpoint_at (CORE_ADDR where)
+static int
+delete_breakpoint_1 (struct process_info *proc, struct breakpoint *todel)
 {
-  struct breakpoint *bp = breakpoints;
+  struct breakpoint *bp, **bp_link;
+  int err;
+
+  bp = proc->breakpoints;
+  bp_link = &proc->breakpoints;
 
-  while (bp != NULL)
+  while (bp)
     {
-      if (bp->pc == where)
-       return bp;
-      bp = bp->next;
+      if (bp == todel)
+       {
+         *bp_link = bp->next;
+
+         err = release_breakpoint (proc, bp);
+         if (err != 0)
+           return err;
+
+         bp = *bp_link;
+         return 0;
+       }
+      else
+       {
+         bp_link = &bp->next;
+         bp = *bp_link;
+       }
     }
 
+  warning ("Could not find breakpoint in list.");
+  return ENOENT;
+}
+
+int
+delete_breakpoint (struct breakpoint *todel)
+{
+  struct process_info *proc = current_process ();
+  return delete_breakpoint_1 (proc, todel);
+}
+
+struct breakpoint *
+find_gdb_breakpoint_at (CORE_ADDR where)
+{
+  struct process_info *proc = current_process ();
+  struct breakpoint *bp;
+
+  for (bp = proc->breakpoints; bp != NULL; bp = bp->next)
+    if (bp->type == gdb_breakpoint && bp->raw->pc == where)
+      return bp;
+
   return NULL;
 }
 
-void
-delete_breakpoint_at (CORE_ADDR addr)
+int
+set_gdb_breakpoint_at (CORE_ADDR where)
 {
-  struct breakpoint *bp = find_breakpoint_at (addr);
+  struct breakpoint *bp;
+
+  if (breakpoint_data == NULL)
+    return 1;
+
+  /* If we see GDB inserting a second breakpoint at the same address,
+     then the first breakpoint must have disappeared due to a shared
+     library unload.  On targets where the shared libraries are
+     handled by userspace, like SVR4, for example, GDBserver can't
+     tell if a library was loaded or unloaded.  Since we refcount
+     breakpoints, if we didn't do this, we'd just increase the
+     refcount of the previous breakpoint at this address, but the trap
+     was not planted in the inferior anymore, thus the breakpoint
+     would never be hit.  */
+  bp = find_gdb_breakpoint_at (where);
   if (bp != NULL)
-    delete_breakpoint (bp);
+    {
+      delete_gdb_breakpoint_at (where);
+
+      /* Might as well validate all other breakpoints.  */
+      validate_breakpoints ();
+    }
+
+  bp = set_breakpoint_at (where, NULL);
+  if (bp == NULL)
+    return -1;
+
+  bp->type = gdb_breakpoint;
+  return 0;
 }
 
-static void
-reinsert_breakpoint_handler (CORE_ADDR stop_pc)
+int
+delete_gdb_breakpoint_at (CORE_ADDR addr)
 {
-  struct breakpoint *stop_bp, *orig_bp;
+  struct breakpoint *bp;
+  int err;
 
-  stop_bp = find_breakpoint_at (stop_pc);
-  if (stop_bp == NULL)
-    error ("lost the stopping breakpoint.");
+  if (breakpoint_data == NULL)
+    return 1;
 
-  orig_bp = stop_bp->breakpoint_to_reinsert;
-  if (orig_bp == NULL)
-    error ("no breakpoint to reinsert");
+  bp = find_gdb_breakpoint_at (addr);
+  if (bp == NULL)
+    return -1;
 
-  (*the_target->write_memory) (orig_bp->pc, breakpoint_data,
-                              breakpoint_len);
-  orig_bp->reinserting = 0;
-  delete_breakpoint (stop_bp);
+  /* Before deleting the breakpoint, make sure to free
+     its condition list.  */
+  clear_gdb_breakpoint_conditions (addr);
+  err = delete_breakpoint (bp);
+  if (err)
+    return -1;
+
+  return 0;
 }
 
+/* Clear all conditions associated with this breakpoint address.  */
+
 void
-reinsert_breakpoint_by_bp (CORE_ADDR stop_pc, CORE_ADDR stop_at)
+clear_gdb_breakpoint_conditions (CORE_ADDR addr)
 {
-  struct breakpoint *bp, *orig_bp;
+  struct breakpoint *bp = find_gdb_breakpoint_at (addr);
+  struct point_cond_list *cond;
 
-  set_breakpoint_at (stop_at, reinsert_breakpoint_handler);
+  if (bp == NULL || bp->cond_list == NULL)
+    return;
 
-  orig_bp = find_breakpoint_at (stop_pc);
-  if (orig_bp == NULL)
-    error ("Could not find original breakpoint in list.");
+  cond = bp->cond_list;
 
-  bp = find_breakpoint_at (stop_at);
-  if (bp == NULL)
-    error ("Could not find breakpoint in list (reinserting by breakpoint).");
-  bp->breakpoint_to_reinsert = orig_bp;
+  while (cond != NULL)
+    {
+      struct point_cond_list *cond_next;
 
-  (*the_target->write_memory) (orig_bp->pc, orig_bp->old_data,
-                              breakpoint_len);
-  orig_bp->reinserting = 1;
+      cond_next = cond->next;
+      free (cond->cond->bytes);
+      free (cond->cond);
+      free (cond);
+      cond = cond_next;
+    }
+
+  bp->cond_list = NULL;
 }
 
+/* Add condition CONDITION to GDBserver's breakpoint BP.  */
+
 void
-uninsert_breakpoint (CORE_ADDR stopped_at)
+add_condition_to_breakpoint (struct breakpoint *bp,
+                            struct agent_expr *condition)
 {
-  struct breakpoint *bp;
+  struct point_cond_list *new_cond;
+
+  /* Create new condition.  */
+  new_cond = xcalloc (1, sizeof (*new_cond));
+  new_cond->cond = condition;
+
+  /* Add condition to the list.  */
+  new_cond->next = bp->cond_list;
+  bp->cond_list = new_cond;
+}
+
+/* Add a target-side condition CONDITION to the breakpoint at ADDR.  */
+
+int
+add_breakpoint_condition (CORE_ADDR addr, char **condition)
+{
+  struct breakpoint *bp = find_gdb_breakpoint_at (addr);
+  char *actparm = *condition;
+  struct agent_expr *cond;
+
+  if (condition == NULL)
+    return 1;
 
-  bp = find_breakpoint_at (stopped_at);
   if (bp == NULL)
-    error ("Could not find breakpoint in list (uninserting).");
+    return 0;
+
+  cond = gdb_parse_agent_expr (&actparm);
 
-  (*the_target->write_memory) (bp->pc, bp->old_data,
-                              breakpoint_len);
-  bp->reinserting = 1;
+  if (cond == NULL)
+    {
+      fprintf (stderr, "Condition evaluation failed. "
+              "Assuming unconditional.\n");
+      return 0;
+    }
+
+  add_condition_to_breakpoint (bp, cond);
+
+  *condition = actparm;
+
+  return 1;
 }
 
-void
-reinsert_breakpoint (CORE_ADDR stopped_at)
+/* Evaluate condition (if any) at breakpoint BP.  Return 1 if
+   true and 0 otherwise.  */
+
+int
+gdb_condition_true_at_breakpoint (CORE_ADDR where)
 {
-  struct breakpoint *bp;
+  /* Fetch registers for the current inferior.  */
+  struct breakpoint *bp = find_gdb_breakpoint_at (where);
+  ULONGEST value = 0;
+  struct point_cond_list *cl;
+  int err = 0;
+  struct eval_agent_expr_context ctx;
 
-  bp = find_breakpoint_at (stopped_at);
   if (bp == NULL)
-    error ("Could not find breakpoint in list (uninserting).");
-  if (! bp->reinserting)
-    error ("Breakpoint already inserted at reinsert time.");
+    return 0;
+
+  /* Check if the breakpoint is unconditional.  If it is,
+     the condition always evaluates to TRUE.  */
+  if (bp->cond_list == NULL)
+    return 1;
+
+  ctx.regcache = get_thread_regcache (current_inferior, 1);
+  ctx.tframe = NULL;
+  ctx.tpoint = NULL;
+
+  /* Evaluate each condition in the breakpoint's list of conditions.
+     Return true if any of the conditions evaluates to TRUE.
+
+     If we failed to evaluate the expression, TRUE is returned.  This
+     forces GDB to reevaluate the conditions.  */
+  for (cl = bp->cond_list;
+       cl && !value && !err; cl = cl->next)
+    {
+      /* Evaluate the condition.  */
+      err = gdb_eval_agent_expr (&ctx, cl->cond, &value);
+    }
+
+  if (err)
+    return 1;
 
-  (*the_target->write_memory) (bp->pc, breakpoint_data,
-                              breakpoint_len);
-  bp->reinserting = 0;
+  return (value != 0);
 }
 
+/* Add commands COMMANDS to GDBserver's breakpoint BP.  */
+
+void
+add_commands_to_breakpoint (struct breakpoint *bp,
+                           struct agent_expr *commands, int persist)
+{
+  struct point_command_list *new_cmd;
+
+  /* Create new command.  */
+  new_cmd = xcalloc (1, sizeof (*new_cmd));
+  new_cmd->cmd = commands;
+  new_cmd->persistence = persist;
+
+  /* Add commands to the list.  */
+  new_cmd->next = bp->command_list;
+  bp->command_list = new_cmd;
+}
+
+/* Add a target-side command COMMAND to the breakpoint at ADDR.  */
+
 int
-check_breakpoints (CORE_ADDR stop_pc)
+add_breakpoint_commands (CORE_ADDR addr, char **command, int persist)
 {
-  struct breakpoint *bp;
+  struct breakpoint *bp = find_gdb_breakpoint_at (addr);
+  char *actparm = *command;
+  struct agent_expr *cmd;
+
+  if (command == NULL)
+    return 1;
 
-  bp = find_breakpoint_at (stop_pc);
   if (bp == NULL)
     return 0;
-  if (bp->reinserting)
+
+  cmd = gdb_parse_agent_expr (&actparm);
+
+  if (cmd == NULL)
     {
-      warning ("Hit a removed breakpoint?");
+      fprintf (stderr, "Command evaluation failed. "
+              "Disabling.\n");
       return 0;
     }
 
-  (*bp->handler) (bp->pc);
+  add_commands_to_breakpoint (bp, cmd, persist);
+
+  *command = actparm;
+
   return 1;
 }
 
+/* Return true if there are no commands to run at this location,
+   which likely means we want to report back to GDB.  */
+int
+gdb_no_commands_at_breakpoint (CORE_ADDR where)
+{
+  struct breakpoint *bp = find_gdb_breakpoint_at (where);
+
+  if (bp == NULL)
+    return 0;
+
+  if (debug_threads)
+    debug_printf ("at 0x%s, bp command_list is 0x%s\n",
+                 paddress (where),
+                 phex_nz ((uintptr_t) bp->command_list, 0));
+  return (bp->command_list == NULL);
+}
+
+void
+run_breakpoint_commands (CORE_ADDR where)
+{
+  /* Fetch registers for the current inferior.  */
+  struct breakpoint *bp = find_gdb_breakpoint_at (where);
+  ULONGEST value = 0;
+  struct point_command_list *cl;
+  int err = 0;
+  struct eval_agent_expr_context ctx;
+
+  if (bp == NULL)
+    return;
+
+  ctx.regcache = get_thread_regcache (current_inferior, 1);
+  ctx.tframe = NULL;
+  ctx.tpoint = NULL;
+
+  for (cl = bp->command_list;
+       cl && !value && !err; cl = cl->next)
+    {
+      /* Run the command.  */
+      err = gdb_eval_agent_expr (&ctx, cl->cmd, &value);
+
+      /* If one command has a problem, stop digging the hole deeper.  */
+      if (err)
+       break;
+    }
+}
+
+/* Return 1 if there is a breakpoint inserted in address WHERE
+   and if its condition, if it exists, is true.  */
+
+int
+gdb_breakpoint_here (CORE_ADDR where)
+{
+  return (find_gdb_breakpoint_at (where) != NULL);
+}
+
+void
+set_reinsert_breakpoint (CORE_ADDR stop_at)
+{
+  struct breakpoint *bp;
+
+  bp = set_breakpoint_at (stop_at, NULL);
+  bp->type = reinsert_breakpoint;
+}
+
+void
+delete_reinsert_breakpoints (void)
+{
+  struct process_info *proc = current_process ();
+  struct breakpoint *bp, **bp_link;
+
+  bp = proc->breakpoints;
+  bp_link = &proc->breakpoints;
+
+  while (bp)
+    {
+      if (bp->type == reinsert_breakpoint)
+       {
+         *bp_link = bp->next;
+         release_breakpoint (proc, bp);
+         bp = *bp_link;
+       }
+      else
+       {
+         bp_link = &bp->next;
+         bp = *bp_link;
+       }
+    }
+}
+
+static void
+uninsert_raw_breakpoint (struct raw_breakpoint *bp)
+{
+  if (bp->inserted)
+    {
+      int err;
+      unsigned char buf[MAX_BREAKPOINT_LEN];
+
+      bp->inserted = 0;
+      /* Since there can be fast tracepoint jumps inserted in the same
+        address range, we use `write_inferior_memory', which takes
+        care of layering breakpoints on top of fast tracepoints, and
+        on top of the buffer we pass it.  This works because we've
+        already unlinked the fast tracepoint jump above.  Also note
+        that we need to pass the current shadow contents, because
+        write_inferior_memory updates any shadow memory with what we
+        pass here, and we want that to be a nop.  */
+      memcpy (buf, bp->old_data, breakpoint_len);
+      err = write_inferior_memory (bp->pc, buf, breakpoint_len);
+      if (err != 0)
+       {
+         bp->inserted = 1;
+
+         if (debug_threads)
+           debug_printf ("Failed to uninsert raw breakpoint at 0x%s (%s).\n",
+                         paddress (bp->pc), strerror (err));
+       }
+    }
+}
+
+void
+uninsert_breakpoints_at (CORE_ADDR pc)
+{
+  struct raw_breakpoint *bp;
+
+  bp = find_raw_breakpoint_at (pc);
+  if (bp == NULL)
+    {
+      /* This can happen when we remove all breakpoints while handling
+        a step-over.  */
+      if (debug_threads)
+       debug_printf ("Could not find breakpoint at 0x%s "
+                     "in list (uninserting).\n",
+                     paddress (pc));
+      return;
+    }
+
+  if (bp->inserted)
+    uninsert_raw_breakpoint (bp);
+}
+
+void
+uninsert_all_breakpoints (void)
+{
+  struct process_info *proc = current_process ();
+  struct raw_breakpoint *bp;
+
+  for (bp = proc->raw_breakpoints; bp != NULL; bp = bp->next)
+    if (bp->inserted)
+      uninsert_raw_breakpoint (bp);
+}
+
+static void
+reinsert_raw_breakpoint (struct raw_breakpoint *bp)
+{
+  int err;
+
+  if (bp->inserted)
+    error ("Breakpoint already inserted at reinsert time.");
+
+  err = (*the_target->write_memory) (bp->pc, breakpoint_data,
+                                    breakpoint_len);
+  if (err == 0)
+    bp->inserted = 1;
+  else if (debug_threads)
+    debug_printf ("Failed to reinsert breakpoint at 0x%s (%s).\n",
+                 paddress (bp->pc), strerror (err));
+}
+
+void
+reinsert_breakpoints_at (CORE_ADDR pc)
+{
+  struct raw_breakpoint *bp;
+
+  bp = find_raw_breakpoint_at (pc);
+  if (bp == NULL)
+    {
+      /* This can happen when we remove all breakpoints while handling
+        a step-over.  */
+      if (debug_threads)
+       debug_printf ("Could not find raw breakpoint at 0x%s "
+                     "in list (reinserting).\n",
+                     paddress (pc));
+      return;
+    }
+
+  reinsert_raw_breakpoint (bp);
+}
+
+void
+reinsert_all_breakpoints (void)
+{
+  struct process_info *proc = current_process ();
+  struct raw_breakpoint *bp;
+
+  for (bp = proc->raw_breakpoints; bp != NULL; bp = bp->next)
+    if (!bp->inserted)
+      reinsert_raw_breakpoint (bp);
+}
+
+void
+check_breakpoints (CORE_ADDR stop_pc)
+{
+  struct process_info *proc = current_process ();
+  struct breakpoint *bp, **bp_link;
+
+  bp = proc->breakpoints;
+  bp_link = &proc->breakpoints;
+
+  while (bp)
+    {
+      if (bp->raw->pc == stop_pc)
+       {
+         if (!bp->raw->inserted)
+           {
+             warning ("Hit a removed breakpoint?");
+             return;
+           }
+
+         if (bp->handler != NULL && (*bp->handler) (stop_pc))
+           {
+             *bp_link = bp->next;
+
+             release_breakpoint (proc, bp);
+
+             bp = *bp_link;
+             continue;
+           }
+       }
+
+      bp_link = &bp->next;
+      bp = *bp_link;
+    }
+}
+
 void
 set_breakpoint_data (const unsigned char *bp_data, int bp_len)
 {
@@ -214,11 +1154,119 @@ set_breakpoint_data (const unsigned char *bp_data, int bp_len)
   breakpoint_len = bp_len;
 }
 
+int
+breakpoint_here (CORE_ADDR addr)
+{
+  return (find_raw_breakpoint_at (addr) != NULL);
+}
+
+int
+breakpoint_inserted_here (CORE_ADDR addr)
+{
+  struct raw_breakpoint *bp;
+
+  bp = find_raw_breakpoint_at (addr);
+
+  return (bp != NULL && bp->inserted);
+}
+
+static int
+validate_inserted_breakpoint (struct raw_breakpoint *bp)
+{
+  unsigned char *buf;
+  int err;
+
+  gdb_assert (bp->inserted);
+
+  buf = alloca (breakpoint_len);
+  err = (*the_target->read_memory) (bp->pc, buf, breakpoint_len);
+  if (err || memcmp (buf, breakpoint_data, breakpoint_len) != 0)
+    {
+      /* Tag it as gone.  */
+      bp->inserted = 0;
+      bp->shlib_disabled = 1;
+      return 0;
+    }
+
+  return 1;
+}
+
+static void
+delete_disabled_breakpoints (void)
+{
+  struct process_info *proc = current_process ();
+  struct breakpoint *bp, *next;
+
+  for (bp = proc->breakpoints; bp != NULL; bp = next)
+    {
+      next = bp->next;
+      if (bp->raw->shlib_disabled)
+       delete_breakpoint_1 (proc, bp);
+    }
+}
+
+/* Check if breakpoints we inserted still appear to be inserted.  They
+   may disappear due to a shared library unload, and worse, a new
+   shared library may be reloaded at the same address as the
+   previously unloaded one.  If that happens, we should make sure that
+   the shadow memory of the old breakpoints isn't used when reading or
+   writing memory.  */
+
+void
+validate_breakpoints (void)
+{
+  struct process_info *proc = current_process ();
+  struct breakpoint *bp;
+
+  for (bp = proc->breakpoints; bp != NULL; bp = bp->next)
+    {
+      if (bp->raw->inserted)
+       validate_inserted_breakpoint (bp->raw);
+    }
+
+  delete_disabled_breakpoints ();
+}
+
 void
 check_mem_read (CORE_ADDR mem_addr, unsigned char *buf, int mem_len)
 {
-  struct breakpoint *bp = breakpoints;
+  struct process_info *proc = current_process ();
+  struct raw_breakpoint *bp = proc->raw_breakpoints;
+  struct fast_tracepoint_jump *jp = proc->fast_tracepoint_jumps;
   CORE_ADDR mem_end = mem_addr + mem_len;
+  int disabled_one = 0;
+
+  for (; jp != NULL; jp = jp->next)
+    {
+      CORE_ADDR bp_end = jp->pc + jp->length;
+      CORE_ADDR start, end;
+      int copy_offset, copy_len, buf_offset;
+
+      gdb_assert (fast_tracepoint_jump_shadow (jp) >= buf + mem_len
+                 || buf >= fast_tracepoint_jump_shadow (jp) + (jp)->length);
+
+      if (mem_addr >= bp_end)
+       continue;
+      if (jp->pc >= mem_end)
+       continue;
+
+      start = jp->pc;
+      if (mem_addr > start)
+       start = mem_addr;
+
+      end = bp_end;
+      if (end > mem_end)
+       end = mem_end;
+
+      copy_len = end - start;
+      copy_offset = start - jp->pc;
+      buf_offset = start - mem_addr;
+
+      if (jp->inserted)
+       memcpy (buf + buf_offset,
+               fast_tracepoint_jump_shadow (jp) + copy_offset,
+               copy_len);
+    }
 
   for (; bp != NULL; bp = bp->next)
     {
@@ -226,6 +1274,9 @@ check_mem_read (CORE_ADDR mem_addr, unsigned char *buf, int mem_len)
       CORE_ADDR start, end;
       int copy_offset, copy_len, buf_offset;
 
+      gdb_assert (bp->old_data >= buf + mem_len
+                 || buf >= &bp->old_data[sizeof (bp->old_data)]);
+
       if (mem_addr >= bp_end)
        continue;
       if (bp->pc >= mem_end)
@@ -243,15 +1294,65 @@ check_mem_read (CORE_ADDR mem_addr, unsigned char *buf, int mem_len)
       copy_offset = start - bp->pc;
       buf_offset = start - mem_addr;
 
-      memcpy (buf + buf_offset, bp->old_data + copy_offset, copy_len);
+      if (bp->inserted)
+       {
+         if (validate_inserted_breakpoint (bp))
+           memcpy (buf + buf_offset, bp->old_data + copy_offset, copy_len);
+         else
+           disabled_one = 1;
+       }
     }
+
+  if (disabled_one)
+    delete_disabled_breakpoints ();
 }
 
 void
-check_mem_write (CORE_ADDR mem_addr, unsigned char *buf, int mem_len)
+check_mem_write (CORE_ADDR mem_addr, unsigned char *buf,
+                const unsigned char *myaddr, int mem_len)
 {
-  struct breakpoint *bp = breakpoints;
+  struct process_info *proc = current_process ();
+  struct raw_breakpoint *bp = proc->raw_breakpoints;
+  struct fast_tracepoint_jump *jp = proc->fast_tracepoint_jumps;
   CORE_ADDR mem_end = mem_addr + mem_len;
+  int disabled_one = 0;
+
+  /* First fast tracepoint jumps, then breakpoint traps on top.  */
+
+  for (; jp != NULL; jp = jp->next)
+    {
+      CORE_ADDR jp_end = jp->pc + jp->length;
+      CORE_ADDR start, end;
+      int copy_offset, copy_len, buf_offset;
+
+      gdb_assert (fast_tracepoint_jump_shadow (jp) >= myaddr + mem_len
+                 || myaddr >= fast_tracepoint_jump_shadow (jp) + (jp)->length);
+      gdb_assert (fast_tracepoint_jump_insn (jp) >= buf + mem_len
+                 || buf >= fast_tracepoint_jump_insn (jp) + (jp)->length);
+
+      if (mem_addr >= jp_end)
+       continue;
+      if (jp->pc >= mem_end)
+       continue;
+
+      start = jp->pc;
+      if (mem_addr > start)
+       start = mem_addr;
+
+      end = jp_end;
+      if (end > mem_end)
+       end = mem_end;
+
+      copy_len = end - start;
+      copy_offset = start - jp->pc;
+      buf_offset = start - mem_addr;
+
+      memcpy (fast_tracepoint_jump_shadow (jp) + copy_offset,
+             myaddr + buf_offset, copy_len);
+      if (jp->inserted)
+       memcpy (buf + buf_offset,
+               fast_tracepoint_jump_insn (jp) + copy_offset, copy_len);
+    }
 
   for (; bp != NULL; bp = bp->next)
     {
@@ -259,6 +1360,9 @@ check_mem_write (CORE_ADDR mem_addr, unsigned char *buf, int mem_len)
       CORE_ADDR start, end;
       int copy_offset, copy_len, buf_offset;
 
+      gdb_assert (bp->old_data >= myaddr + mem_len
+                 || myaddr >= &bp->old_data[sizeof (bp->old_data)]);
+
       if (mem_addr >= bp_end)
        continue;
       if (bp->pc >= mem_end)
@@ -276,17 +1380,54 @@ check_mem_write (CORE_ADDR mem_addr, unsigned char *buf, int mem_len)
       copy_offset = start - bp->pc;
       buf_offset = start - mem_addr;
 
-      memcpy (bp->old_data + copy_offset, buf + buf_offset, copy_len);
-      if (bp->reinserting == 0)
-       memcpy (buf + buf_offset, breakpoint_data + copy_offset, copy_len);
+      memcpy (bp->old_data + copy_offset, myaddr + buf_offset, copy_len);
+      if (bp->inserted)
+       {
+         if (validate_inserted_breakpoint (bp))
+           memcpy (buf + buf_offset, breakpoint_data + copy_offset, copy_len);
+         else
+           disabled_one = 1;
+       }
     }
+
+  if (disabled_one)
+    delete_disabled_breakpoints ();
 }
 
-/* Delete all breakpoints.  */
+/* Delete all breakpoints, and un-insert them from the inferior.  */
 
 void
 delete_all_breakpoints (void)
 {
-  while (breakpoints)
-    delete_breakpoint (breakpoints);
+  struct process_info *proc = current_process ();
+
+  while (proc->breakpoints)
+    delete_breakpoint_1 (proc, proc->breakpoints);
+}
+
+/* Clear the "inserted" flag in all breakpoints.  */
+
+void
+mark_breakpoints_out (struct process_info *proc)
+{
+  struct raw_breakpoint *raw_bp;
+
+  for (raw_bp = proc->raw_breakpoints; raw_bp != NULL; raw_bp = raw_bp->next)
+    raw_bp->inserted = 0;
+}
+
+/* Release all breakpoints, but do not try to un-insert them from the
+   inferior.  */
+
+void
+free_all_breakpoints (struct process_info *proc)
+{
+  mark_breakpoints_out (proc);
+
+  /* Note: use PROC explicitly instead of deferring to
+     delete_all_breakpoints --- CURRENT_INFERIOR may already have been
+     released when we get here.  There should be no call to
+     current_process from here on.  */
+  while (proc->breakpoints)
+    delete_breakpoint_1 (proc, proc->breakpoints);
 }
This page took 0.041765 seconds and 4 git commands to generate.