diff mbox

[v2,4/8] Put single-step breakpoints on the bp_location chain

Message ID 1412344458-31774-5-git-send-email-palves@redhat.com
State New
Headers show

Commit Message

Pedro Alves Oct. 3, 2014, 1:54 p.m. UTC
This patch makes single-step breakpoints "real" breakpoints on the
global location list.

There are several benefits to this:

- It removes the currently limitation that only 2 single-step
  breakpoints can be inserted.  See an example here of a discussion
  around a case that wants more than 2, possibly unbounded:

  https://sourceware.org/ml/gdb-patches/2014-03/msg00663.html

- makes software single-step work on read-only code regions.

  The logic to convert a software breakpoint to a hardware breakpoint
  if the memory map says the breakpoint address is in read only memory
  is in insert_bp_location.  Because software single-step breakpoints
  bypass all that go and straight to target_insert_breakpoint, we
  can't software single-step over read only memory.  This patch
  removes that limitation, and adds a test that makes sure that works,
  by forcing a code region to read-only with "mem LOW HIGH ro" and
  then stepping through that.

- Fixes PR breakpoints/9649

  This is an assertion failure in insert_single_step_breakpoint in
  breakpoint.c, because we may leave stale single-step breakpoints
  behind on error.

  The tests for stepping through read-only regions exercise the root
  cause of the bug, which is that we leave single-step breakpoints
  behind if we fail to insert any single-step breakpoint.  Deleting
  the single-step breakpoints in resume_cleanups,
  delete_just_stopped_threads_infrun_breakpoints, and
  fetch_inferior_event fixes this.  Without that, we'd no longer hit
  the assertion, as that code is deleted, but we'd instead run into
  errors/warnings trying to insert/remove the stale breakpoints on
  next resume.

- Paves the way to have multiple threads software single-stepping at
  the same time, leaving update_global_location_list to worry about
  duplicate locations.

- Makes the moribund location machinery aware of software single-step
  breakpoints, paving the way to enable software single-step on
  non-stop, instead of forcing serialized displaced stepping for all
  single steps.

- It's generaly cleaner.

  We no longer have to play games with single-step breakpoints
  inserted at the same address as regular breakpoints, like we
  recently had to do for 7.8.  See this discussion:

  https://sourceware.org/ml/gdb-patches/2014-06/msg00052.html.

Tested on x86_64 Fedora 20, on top of my 'single-step breakpoints on
x86' series.

gdb/
2014-10-03  Pedro Alves  <palves@redhat.com>

	PR breakpoints/9649
	* breakpoint.c (single_step_breakpoints, single_step_gdbarch):
	Delete array globals.
	(single_step_breakpoints): New global.
	(breakpoint_xfer_memory): Remove special handling for single-step
	breakpoints.
	(update_breakpoints_after_exec): Delete bp_single_step
	breakpoints.
	(detach_breakpoints): Remove special handling for single-step
	breakpoints.
	(breakpoint_init_inferior): Delete bp_single_step breakpoints.
	(bpstat_stop_status): Add comment.
	(bpstat_what, bptype_string, print_one_breakpoint_location)
	(adjust_breakpoint_address, init_bp_location): Handle
	bp_single_step.
	(new_single_step_breakpoint): New function.
	(set_momentary_breakpoint, bkpt_remove_location): Remove special
	handling for single-step breakpoints.
	(insert_single_step_breakpoint, single_step_breakpoints_inserted)
	(remove_single_step_breakpoints, cancel_single_step_breakpoints):
	Rewrite.
	(detach_single_step_breakpoints, find_single_step_breakpoint):
	Delete functions.
	(breakpoint_has_location_inserted_here): New function.
	(single_step_breakpoint_inserted_here_p): Rewrite.
	* breakpoint.h: Remove FIXME.
	(enum bptype) <bp_single_step>: New enum value.
	(insert_single_step_breakpoint): Update comment.
	* infrun.c (resume_cleanups)
	(delete_step_thread_step_resume_breakpoint): Remove single-step
	breakpoints.
	(fetch_inferior_event): Install a cleanup that removes infrun
	breakpoints.
	(switch_back_to_stepped_thread) <expect thread advanced also>:
	Clear step-over info.

gdb/testsuite/
2014-10-03  Pedro Alves  <palves@redhat.com>

	PR breakpoints/9649
	* gdb.base/breakpoint-in-ro-region.c (main): Add more instructions.
	* gdb.base/breakpoint-in-ro-region.exp
	(probe_target_hardware_step): New procedure.
	(top level): Probe hardware stepping and hardware breakpoint
	support.  Test stepping through a read-only region, with both
	"breakpoint auto-hw" on and off and both "always-inserted" on and
	off.
---
 gdb/breakpoint.c                                   | 226 ++++++++-------------
 gdb/breakpoint.h                                   |  13 +-
 gdb/infrun.c                                       |  16 ++
 gdb/testsuite/gdb.base/breakpoint-in-ro-region.c   |   9 +
 gdb/testsuite/gdb.base/breakpoint-in-ro-region.exp | 104 ++++++++++
 5 files changed, 221 insertions(+), 147 deletions(-)
diff mbox

Patch

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 08ec533..10010f8 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -226,11 +226,6 @@  static void stopat_command (char *arg, int from_tty);
 
 static void tcatch_command (char *arg, int from_tty);
 
-static void detach_single_step_breakpoints (void);
-
-static int find_single_step_breakpoint (struct address_space *aspace,
-					CORE_ADDR pc);
-
 static void free_bp_location (struct bp_location *loc);
 static void incref_bp_location (struct bp_location *loc);
 static void decref_bp_location (struct bp_location **loc);
@@ -334,8 +329,7 @@  struct breakpoint_ops dprintf_breakpoint_ops;
 /* One (or perhaps two) breakpoints used for software single
    stepping.  */
 
-static void *single_step_breakpoints[2];
-static struct gdbarch *single_step_gdbarch[2];
+static struct breakpoint *single_step_breakpoints;
 
 /* The style in which to perform a dynamic printf.  This is a user
    option because different output options have different tradeoffs;
@@ -1665,21 +1659,6 @@  breakpoint_xfer_memory (gdb_byte *readbuf, gdb_byte *writebuf,
     one_breakpoint_xfer_memory (readbuf, writebuf, writebuf_org,
 				memaddr, len, &bl->target_info, bl->gdbarch);
   }
-
-  /* Now process single-step breakpoints.  These are not found in the
-     bp_location array.  */
-  for (i = 0; i < 2; i++)
-    {
-      struct bp_target_info *bp_tgt = single_step_breakpoints[i];
-
-      if (bp_tgt != NULL)
-	{
-	  struct gdbarch *gdbarch = single_step_gdbarch[i];
-
-	  one_breakpoint_xfer_memory (readbuf, writebuf, writebuf_org,
-				      memaddr, len, bp_tgt, gdbarch);
-	}
-    }
 }
 
 
@@ -3793,6 +3772,13 @@  update_breakpoints_after_exec (void)
 	continue;
       }
 
+    /* Just like single-step breakpoints.  */
+    if (b->type == bp_single_step)
+      {
+	delete_breakpoint (b);
+	continue;
+      }
+
     /* Longjmp and longjmp-resume breakpoints are also meaningless
        after an exec.  */
     if (b->type == bp_longjmp || b->type == bp_longjmp_resume
@@ -3885,9 +3871,6 @@  detach_breakpoints (ptid_t ptid)
       val |= remove_breakpoint_1 (bl, mark_inserted);
   }
 
-  /* Detach single-step breakpoints as well.  */
-  detach_single_step_breakpoints ();
-
   do_cleanups (old_chain);
   return val;
 }
@@ -4157,6 +4140,10 @@  breakpoint_init_inferior (enum inf_context context)
 
 	/* Also remove step-resume breakpoints.  */
 
+      case bp_single_step:
+
+	/* Also remove single-step breakpoints.  */
+
 	delete_breakpoint (b);
 	break;
 
@@ -5633,6 +5620,7 @@  bpstat_stop_status (struct address_space *aspace,
 	}
     }
 
+  /* Check if a moribund breakpoint explains the stop.  */
   for (ix = 0; VEC_iterate (bp_location_p, moribund_locations, ix, loc); ++ix)
     {
       if (breakpoint_location_address_match (loc, aspace, bp_addr))
@@ -5788,6 +5776,7 @@  bpstat_what (bpstat bs_head)
 	  break;
 	case bp_breakpoint:
 	case bp_hardware_breakpoint:
+	case bp_single_step:
 	case bp_until:
 	case bp_finish:
 	case bp_shlib_event:
@@ -6146,6 +6135,7 @@  bptype_string (enum bptype type)
     {bp_none, "?deleted?"},
     {bp_breakpoint, "breakpoint"},
     {bp_hardware_breakpoint, "hw breakpoint"},
+    {bp_single_step, "sw single-step"},
     {bp_until, "until"},
     {bp_finish, "finish"},
     {bp_watchpoint, "watchpoint"},
@@ -6337,6 +6327,7 @@  print_one_breakpoint_location (struct breakpoint *b,
 
       case bp_breakpoint:
       case bp_hardware_breakpoint:
+      case bp_single_step:
       case bp_until:
       case bp_finish:
       case bp_longjmp:
@@ -7179,6 +7170,16 @@  adjust_breakpoint_address (struct gdbarch *gdbarch,
          have their addresses modified.  */
       return bpaddr;
     }
+  else if (bptype == bp_single_step)
+    {
+      /* Single-step breakpoints should not have their addresses
+	 modified.  If there's any architectural constrain that
+	 applies to this address, then it should have already been
+	 taken into account when the breakpoint was created in the
+	 first place.  If we didn't do this, stepping through e.g.,
+	 Thumb-2 IT blocks would break.  */
+      return bpaddr;
+    }
   else
     {
       CORE_ADDR adjusted_bpaddr;
@@ -7215,6 +7216,7 @@  init_bp_location (struct bp_location *loc, const struct bp_location_ops *ops,
   switch (owner->type)
     {
     case bp_breakpoint:
+    case bp_single_step:
     case bp_until:
     case bp_finish:
     case bp_longjmp:
@@ -9197,10 +9199,31 @@  enable_breakpoints_after_startup (void)
   breakpoint_re_set ();
 }
 
+/* Create a new single-step breakpoint for thread THREAD, with no
+   locations.  */
 
-/* Set a breakpoint that will evaporate an end of command
-   at address specified by SAL.
-   Restrict it to frame FRAME if FRAME is nonzero.  */
+static struct breakpoint *
+new_single_step_breakpoint (int thread, struct gdbarch *gdbarch)
+{
+  struct breakpoint *b = XNEW (struct breakpoint);
+
+  init_raw_breakpoint_without_location (b, gdbarch, bp_single_step,
+					&momentary_breakpoint_ops);
+
+  b->disposition = disp_donttouch;
+  b->frame_id = null_frame_id;
+
+  b->thread = thread;
+  gdb_assert (b->thread != 0);
+
+  add_to_breakpoint_chain (b);
+
+  return b;
+}
+
+/* Set a momentary breakpoint of type TYPE at address specified by
+   SAL.  If FRAME_ID is valid, the breakpoint is restricted to that
+   frame.  */
 
 struct breakpoint *
 set_momentary_breakpoint (struct gdbarch *gdbarch, struct symtab_and_line sal,
@@ -13325,28 +13348,9 @@  static int
 bkpt_insert_location (struct bp_location *bl)
 {
   if (bl->loc_type == bp_loc_hardware_breakpoint)
-    return target_insert_hw_breakpoint (bl->gdbarch,
-					&bl->target_info);
+    return target_insert_hw_breakpoint (bl->gdbarch, &bl->target_info);
   else
-    {
-      struct bp_target_info *bp_tgt = &bl->target_info;
-      int ret;
-      int sss_slot;
-
-      /* There is no need to insert a breakpoint if an unconditional
-	 raw/sss breakpoint is already inserted at that location.  */
-      sss_slot = find_single_step_breakpoint (bp_tgt->placed_address_space,
-					      bp_tgt->reqstd_address);
-      if (sss_slot >= 0)
-	{
-	  struct bp_target_info *sss_bp_tgt = single_step_breakpoints[sss_slot];
-
-	  bp_target_info_copy_insertion_state (bp_tgt, sss_bp_tgt);
-	  return 0;
-	}
-
-      return target_insert_breakpoint (bl->gdbarch, bp_tgt);
-    }
+    return target_insert_breakpoint (bl->gdbarch, &bl->target_info);
 }
 
 static int
@@ -13355,19 +13359,7 @@  bkpt_remove_location (struct bp_location *bl)
   if (bl->loc_type == bp_loc_hardware_breakpoint)
     return target_remove_hw_breakpoint (bl->gdbarch, &bl->target_info);
   else
-    {
-      struct bp_target_info *bp_tgt = &bl->target_info;
-      struct address_space *aspace = bp_tgt->placed_address_space;
-      CORE_ADDR address = bp_tgt->reqstd_address;
-
-      /* Only remove the breakpoint if there is no raw/sss breakpoint
-	 still inserted at this location.  Otherwise, we would be
-	 effectively disabling the raw/sss breakpoint.  */
-      if (single_step_breakpoint_inserted_here_p (aspace, address))
-	return 0;
-
-      return target_remove_breakpoint (bl->gdbarch, bp_tgt);
-    }
+    return target_remove_breakpoint (bl->gdbarch, &bl->target_info);
 }
 
 static int
@@ -15453,31 +15445,20 @@  insert_single_step_breakpoint (struct gdbarch *gdbarch,
 			       struct address_space *aspace, 
 			       CORE_ADDR next_pc)
 {
-  void **bpt_p;
+  struct thread_info *tp = inferior_thread ();
+  struct symtab_and_line sal;
+  CORE_ADDR pc = next_pc;
 
-  if (single_step_breakpoints[0] == NULL)
-    {
-      bpt_p = &single_step_breakpoints[0];
-      single_step_gdbarch[0] = gdbarch;
-    }
-  else
-    {
-      gdb_assert (single_step_breakpoints[1] == NULL);
-      bpt_p = &single_step_breakpoints[1];
-      single_step_gdbarch[1] = gdbarch;
-    }
+  if (single_step_breakpoints == NULL)
+    single_step_breakpoints = new_single_step_breakpoint (tp->num, gdbarch);
 
-  /* NOTE drow/2006-04-11: A future improvement to this function would
-     be to only create the breakpoints once, and actually put them on
-     the breakpoint chain.  That would let us use set_raw_breakpoint.
-     We could adjust the addresses each time they were needed.  Doing
-     this requires corresponding changes elsewhere where single step
-     breakpoints are handled, however.  So, for now, we use this.  */
+  sal = find_pc_line (pc, 0);
+  sal.pc = pc;
+  sal.section = find_pc_overlay (pc);
+  sal.explicit_pc = 1;
+  add_location_to_breakpoint (single_step_breakpoints, &sal);
 
-  *bpt_p = deprecated_insert_raw_breakpoint (gdbarch, aspace, next_pc);
-  if (*bpt_p == NULL)
-    error (_("Could not insert single-step breakpoint at %s"),
-	     paddress (gdbarch, next_pc));
+  update_global_location_list (UGLL_INSERT);
 }
 
 /* Check if the breakpoints used for software single stepping
@@ -15486,8 +15467,7 @@  insert_single_step_breakpoint (struct gdbarch *gdbarch,
 int
 single_step_breakpoints_inserted (void)
 {
-  return (single_step_breakpoints[0] != NULL
-          || single_step_breakpoints[1] != NULL);
+  return (single_step_breakpoints != NULL);
 }
 
 /* Remove and delete any breakpoints used for software single step.  */
@@ -15495,22 +15475,11 @@  single_step_breakpoints_inserted (void)
 void
 remove_single_step_breakpoints (void)
 {
-  gdb_assert (single_step_breakpoints[0] != NULL);
+  gdb_assert (single_step_breakpoints != NULL);
 
-  /* See insert_single_step_breakpoint for more about this deprecated
-     call.  */
-  deprecated_remove_raw_breakpoint (single_step_gdbarch[0],
-				    single_step_breakpoints[0]);
-  single_step_gdbarch[0] = NULL;
-  single_step_breakpoints[0] = NULL;
+  delete_breakpoint (single_step_breakpoints);
 
-  if (single_step_breakpoints[1] != NULL)
-    {
-      deprecated_remove_raw_breakpoint (single_step_gdbarch[1],
-					single_step_breakpoints[1]);
-      single_step_gdbarch[1] = NULL;
-      single_step_breakpoints[1] = NULL;
-    }
+  single_step_breakpoints = NULL;
 }
 
 /* Delete software single step breakpoints without removing them from
@@ -15521,51 +15490,28 @@  remove_single_step_breakpoints (void)
 void
 cancel_single_step_breakpoints (void)
 {
-  int i;
-
-  for (i = 0; i < 2; i++)
-    if (single_step_breakpoints[i])
-      {
-	xfree (single_step_breakpoints[i]);
-	single_step_breakpoints[i] = NULL;
-	single_step_gdbarch[i] = NULL;
-      }
-}
-
-/* Detach software single-step breakpoints from INFERIOR_PTID without
-   removing them.  */
-
-static void
-detach_single_step_breakpoints (void)
-{
-  int i;
-
-  for (i = 0; i < 2; i++)
-    if (single_step_breakpoints[i])
-      target_remove_breakpoint (single_step_gdbarch[i],
-				single_step_breakpoints[i]);
+  /* We don't really need to (or should) delete them here.  After an
+     exit, breakpoint_init_inferior deletes it.  After an exec,
+     update_breakpoints_after_exec does it.  Just clear our
+     reference.  */
+  single_step_breakpoints = NULL;
 }
 
-/* Find the software single-step breakpoint that inserted at PC.
-   Returns its slot if found, and -1 if not found.  */
+/* Check whether any location of BP is inserted at PC.  */
 
 static int
-find_single_step_breakpoint (struct address_space *aspace,
-			     CORE_ADDR pc)
+breakpoint_has_location_inserted_here (struct breakpoint *bp,
+				       struct address_space *aspace,
+				       CORE_ADDR pc)
 {
-  int i;
+  struct bp_location *loc;
 
-  for (i = 0; i < 2; i++)
-    {
-      struct bp_target_info *bp_tgt = single_step_breakpoints[i];
-      if (bp_tgt
-	  && breakpoint_address_match (bp_tgt->placed_address_space,
-				       bp_tgt->reqstd_address,
-				       aspace, pc))
-	return i;
-    }
+  for (loc = bp->loc; loc != NULL; loc = loc->next)
+    if (loc->inserted
+	&& breakpoint_location_address_match (loc, aspace, pc))
+      return 1;
 
-  return -1;
+  return 0;
 }
 
 /* Check whether a software single-step breakpoint is inserted at
@@ -15575,7 +15521,9 @@  int
 single_step_breakpoint_inserted_here_p (struct address_space *aspace,
 					CORE_ADDR pc)
 {
-  return find_single_step_breakpoint (aspace, pc) >= 0;
+  return (single_step_breakpoints != NULL
+	  && breakpoint_has_location_inserted_here (single_step_breakpoints,
+						    aspace, pc));
 }
 
 /* Returns 0 if 'bp' is NOT a syscall catchpoint,
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index b611057..7c563c1 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -47,18 +47,13 @@  struct linespec_sals;
 
 
 /* Type of breakpoint.  */
-/* FIXME In the future, we should fold all other breakpoint-like
-   things into here.  This includes:
-
-   * single-step (for machines where we have to simulate single
-   stepping) (probably, though perhaps it is better for it to look as
-   much as possible like a single-step to wait_for_inferior).  */
 
 enum bptype
   {
     bp_none = 0,		/* Eventpoint has been deleted */
     bp_breakpoint,		/* Normal breakpoint */
     bp_hardware_breakpoint,	/* Hardware assisted breakpoint */
+    bp_single_step,		/* Software single-step */
     bp_until,			/* used by until command */
     bp_finish,			/* used by finish command */
     bp_watchpoint,		/* Watchpoint */
@@ -1461,8 +1456,10 @@  extern void add_solib_catchpoint (char *arg, int is_load, int is_temp,
    deletes all breakpoints.  */
 extern void delete_command (char *arg, int from_tty);
 
-/* Manage a software single step breakpoint (or two).  Insert may be
-   called twice before remove is called.  */
+/* Create and insert a new software single step breakpoint for the
+   current thread.  May be called multiple times; each time will add a
+   new location to the set of potential addresses the next instruction
+   is at.  */
 extern void insert_single_step_breakpoint (struct gdbarch *,
 					   struct address_space *, 
 					   CORE_ADDR);
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 3f32d24..0948a5b 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1919,6 +1919,9 @@  infrun_thread_ptid_changed (ptid_t old_ptid, ptid_t new_ptid)
 static void
 resume_cleanups (void *ignore)
 {
+  if (single_step_breakpoints_inserted ())
+    remove_single_step_breakpoints ();
+
   normal_stop ();
 }
 
@@ -2894,6 +2897,9 @@  static void
 delete_just_stopped_threads_infrun_breakpoints (void)
 {
   for_each_just_stopped_thread (delete_thread_infrun_breakpoints);
+
+  if (single_step_breakpoints_inserted ())
+    remove_single_step_breakpoints ();
 }
 
 /* A cleanup wrapper.  */
@@ -3153,6 +3159,8 @@  fetch_inferior_event (void *client_data)
      still for the thread which has thrown the exception.  */
   make_bpstat_clear_actions_cleanup ();
 
+  make_cleanup (delete_just_stopped_threads_infrun_breakpoints_cleanup, NULL);
+
   /* Now figure out what to do with the result of the result.  */
   handle_inferior_event (ecs);
 
@@ -5524,6 +5532,14 @@  switch_back_to_stepped_thread (struct execution_control_state *ecs)
 		fprintf_unfiltered (gdb_stdlog,
 				    "infrun: expected thread advanced also\n");
 
+	      /* Clear the info of the previous step-over, as it's no
+		 longer valid.  It's what keep_going would do too, if
+		 we called it.  Must do this before trying to insert
+		 the sss breakpoint, otherwise if we were previously
+		 trying to step over this exact address in another
+		 thread, the breakpoint ends up not installed.  */
+	      clear_step_over_info ();
+
 	      insert_single_step_breakpoint (get_frame_arch (frame),
 					     get_frame_address_space (frame),
 					     stop_pc);
diff --git a/gdb/testsuite/gdb.base/breakpoint-in-ro-region.c b/gdb/testsuite/gdb.base/breakpoint-in-ro-region.c
index 696a67d..2a999b1 100644
--- a/gdb/testsuite/gdb.base/breakpoint-in-ro-region.c
+++ b/gdb/testsuite/gdb.base/breakpoint-in-ro-region.c
@@ -23,6 +23,15 @@  main (void)
   i = 0;
   i = 0;
   i = 0;
+  i = 0;
+  i = 0;
+  i = 0;
+  i = 0;
+  i = 0;
+  i = 0;
+  i = 0;
+  i = 0;
+  i = 0;
 
   return 0;
 }
diff --git a/gdb/testsuite/gdb.base/breakpoint-in-ro-region.exp b/gdb/testsuite/gdb.base/breakpoint-in-ro-region.exp
index 8eacefa..1991aa5 100644
--- a/gdb/testsuite/gdb.base/breakpoint-in-ro-region.exp
+++ b/gdb/testsuite/gdb.base/breakpoint-in-ro-region.exp
@@ -27,6 +27,28 @@  if ![runto main] {
 
 delete_breakpoints
 
+# Probe for hardware stepping.
+
+proc probe_target_hardware_step {} {
+    global gdb_prompt
+
+    set hw_step 0
+
+    gdb_test_no_output "set debug target 1"
+    set test "probe target hardware step"
+    gdb_test_multiple "si" $test {
+	-re "to_resume \\(\[^\r\n\]+, step, .*$gdb_prompt $" {
+	    set hw_step 1
+	    pass $test
+	}
+	-re "$gdb_prompt $" {
+	    pass $test
+	}
+    }
+    gdb_test "set debug target 0" "->to_log_command.*\\).*"
+    return $hw_step
+}
+
 # Get the bounds of a function, and write them to FUNC_LO (inclusive),
 # FUNC_HI (exclusive).  Return true on success and false on failure.
 proc get_function_bounds {function func_lo func_hi} {
@@ -108,6 +130,7 @@  proc get_next_insn {} {
     return $next
 }
 
+set hw_step [probe_target_hardware_step]
 
 if ![get_function_bounds "main" main_lo main_hi] {
     # Can't do the following tests if main's bounds are unknown.
@@ -140,3 +163,84 @@  gdb_test "p /x *(char *) $main_lo = 1" \
 gdb_test "break *$main_lo" \
     "Cannot insert breakpoint .*Cannot set software breakpoint at read-only address $main_lo.*" \
     "inserting software breakpoint in read-only memory fails"
+
+delete_breakpoints
+
+set supports_hbreak 0
+set test "probe hbreak support"
+gdb_test_multiple "hbreak *$main_lo" $test {
+    -re "You may have requested too many.*$gdb_prompt $" {
+	pass "$test (no support)"
+    }
+    -re "No hardware breakpoint support.*$gdb_prompt $" {
+	pass "$test (no support)"
+    }
+    -re "$gdb_prompt $" {
+	pass "$test (support)"
+	set supports_hbreak 1
+    }
+}
+
+delete_breakpoints
+
+# Check that the "auto-hw on/off" setting affects single-step
+# breakpoints as expected, by stepping through the read-only region.
+# If the target does hardware stepping, we won't exercise that aspect,
+# but we should be able to step through the region without seeing the
+# hardware breakpoint or read-only address errors.
+proc test_single_step { always_inserted auto_hw } {
+    global gdb_prompt
+    global decimal
+    global supports_hbreak
+    global hw_step
+
+    gdb_test_no_output "set breakpoint always-inserted $always_inserted"
+    gdb_test_no_output "set breakpoint auto-hw $auto_hw"
+
+    # Get the address of the current instruction so we know where SI is
+    # starting from.
+    set curr_insn [get_curr_insn]
+
+    # Get the address of the next instruction so we know where SI should
+    # land.
+    set next_insn [get_next_insn]
+
+    set test "step in ro region"
+    gdb_test_multiple "si" $test {
+	-re "Could not insert hardware breakpoints.*$gdb_prompt $" {
+	    gdb_assert {!$hw_step && $auto_hw == "on" && !$supports_hbreak} \
+		"$test (cannot insert hw break)"
+	}
+	-re "Cannot set software breakpoint at read-only address $next_insn.*$gdb_prompt $" {
+	    gdb_assert {!$hw_step && $auto_hw == "off"} \
+		"$test (cannot insert sw break)"
+	}
+	-re "^si\r\nNote: automatically using hardware breakpoints for read-only addresses\.\r\n${decimal}\[ \t\]+i = 0;\r\n$gdb_prompt $" {
+	    gdb_assert {!$hw_step && $auto_hw == "on" && $supports_hbreak} \
+		"$test (auto-hw)"
+	}
+	-re "^si\r\n${decimal}\[ \t\]+i = 0;\r\n$gdb_prompt $" {
+	    gdb_assert {$hw_step || ($auto_hw == "on" && $supports_hbreak)} \
+		"$test (no error)"
+	}
+    }
+
+    gdb_test "maint info breakpoints 0" \
+	"No breakpoint or watchpoint matching '0'\." \
+	"single-step breakpoint is not left behind"
+
+    # Confirm the thread really advanced.
+    if {$hw_step || ($auto_hw == "on" && $supports_hbreak)} {
+	gdb_test "p /x \$pc" " = $next_insn" "thread advanced"
+    } else {
+	gdb_test "p /x \$pc" " = $curr_insn" "thread did not advance"
+    }
+}
+
+foreach always_inserted {"off" "on"} {
+    foreach auto_hw {"off" "on"} {
+	with_test_prefix "always-inserted $always_inserted: auto-hw $auto_hw" {
+	    test_single_step $always_inserted $auto_hw
+	}
+    }
+}