This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH 3/3] fix skipping permanent breakpoints


On 11/05/2014 12:26 PM, Yao Qi wrote:
> Pedro Alves <palves@redhat.com> writes:
> 
>> The patch also adds a new gdb.base/bp-permanent.exp test that
>> exercises many different code paths related to stepping permanent
>> breakpoints, including the stepping with signals cases.  The test uses
>> "hack/trick" to make it work on all (or most) platforms -- it doesn't
>> really hard code a breakpoint instruction.
> 
> It is great to test permanent breakpoint in an arch-independent way.  It
> exposes an existing issue on permanent breakpoint which is found when I

Nice.  That is catches bugs, that is.  :-)

> test your patch series on arm.  breakpoint.c:bp_location_has_shadow
> should return false for permanent breakpoint because permanent
> breakpoint doesn't have shadow, otherwise, breakpoint_xfer_memory
> destroys contents read from memory_xfer_partial_1 and the internal error
> is triggered as below,

I wonder why we don't see that on x86.

> 
> (gdb) PASS: gdb.base/bp-permanent.exp: always_inserted=off, sw_watchpoint=0: stepi signal with handler: queue-signal SIGUSR1
> stepi^M
> ../../../git/gdb/infrun.c:2112: internal-error: resume: Assertion `tp->control.step_resume_breakpoint->loc->permanent' failed.^M
> A problem internal to GDB has been detected,

> 
>> +      else
>> +	{
>> +	  /* There's no signal to pass.  Skip the permanent
>> +	     breakpoint, and arrange for any breakpoint at the new PC
>> +	     to be reported back to handle_inferior_event.  */
>> +
>> +	  if (debug_infrun)
>> +	    fprintf_unfiltered (gdb_stdlog,
>> +				"infrun: resume: skipping permanent breakpoint\n");
>> +	  gdbarch_skip_permanent_breakpoint (gdbarch, regcache);
>> +	  /* Update pc to reflect the new address from which we will
>> +	     execute instructions.  */
>> +	  pc = regcache_read_pc (get_thread_regcache (inferior_ptid));
> 
> Is get_thread_regcache (inferior_ptid) equivalent to regcache?  If it
> is, we can use regcache which is shorter.

Indeed.

> 
>> +	  tp->prev_pc = pc;
>> +
>> +	  clear_step_over_info ();
>> +	  tp->control.trap_expected = 0;
>> +
>> +	  /* There may or not be a breakpoint at the new address.  If
>> +	     we're stepping then we're done.  Otherwise, make sure
>> +	     there's a breakpoint at the current address, and let it
>> +	     trigger.  */
> 
> Sorry, I can't map the comments to the code.  Is "Otherwise" in the
> comments about the else branch which doesn't exist?  Can you
> elaborate?

I'll do better and rewrite this piece of code.  :-P  That comment
was referring to a somewhat earlier version of the patch, and doesn't
really make sense.

> 
>> +	  if (step)
>> +	    {
>> +	      /* If we already have a step-resume breakpoint set, then
>> +		 we can just continue until that one is hit.  */
> 
> When I read the comments, the code in my mind is like:
> 
> if (tp->control.step_resume_breakpoint != NULL)
>   step = 0;
> 
> which is different from your code below.

Yeah, sorry about that.

> 
>> +	      if (tp->control.step_resume_breakpoint == NULL)
>> +		insert_step_resume_breakpoint_at_frame (get_current_frame ());
>> +	      step = 0;

I've thought this over some more, and realized two things:

If there was already an outermost step-resume breakpoint set (we're
already skipping a function for "next", and tripped on a permanent
breakpoint), we shouldn't clear step, as that'll make us miss
software watchpoints.

We'd avoid this issue if we could have more than one step-resume
breakpoint installed for the same thread, that isn't allowed for
good reason.  Thinking again, we have a better breakpoint type
for this -- a software single-step breakpoint.  We're implementing
single-step ourselves, so sounds like a perfect fit.  The only
issue is that a couple paths in infrun.c we need are guarded by 
gdbarch_software_single_step_p.  Those are optimization guards.
Given we need that code on hardware step targets too, we just
remove them...  (I'll reindent in the final version.)

>> +    if ![target_info exists gdb,nosignals] {
>> +
> 
> We need also check can_single_step_to_signal_handler.

Indeed.

Let me know what you think of this version.


------

>From 77e408373ac276bbdf193076b4a04a86400aecf4 Mon Sep 17 00:00:00 2001
From: Pedro Alves <palves@redhat.com>
Date: Fri, 7 Nov 2014 19:09:58 +0000
Subject: [PATCH] fix skipping permanent breakpoints

The gdb.arch/i386-bp_permanent.exp test is currently failing an
assertion I've recently added:

 (gdb) stepi
 ../../src/gdb/infrun.c:2237: internal-error: resume: Assertion `sig != GDB_SIGNAL_0' failed.
 A problem internal to GDB has been detected,
 further debugging may prove unreliable.
 Quit this debugging session? (y or n)
 FAIL: gdb.arch/i386-bp_permanent.exp: Single stepping past permanent breakpoint. (GDB internal error)

The assertion expects that the only reason we currently need to step a
breakpoint instruction is when we have a signal to deliver.  But when
stepping a permanent breakpoint (with or without a signal) we also
reach this code.

IMO, the assertion is correct and the permanent breakpoints skipping
code is wrong.

Consider the case of the user doing "step/stepi" when stopped at a
permanent breakpoint.  GDB's `resume' calls the
gdbarch_skip_permanent_breakpoint hook and then happily continues
stepping:

  /* Normally, by the time we reach `resume', the breakpoints are either
     removed or inserted, as appropriate.  The exception is if we're sitting
     at a permanent breakpoint; we need to step over it, but permanent
     breakpoints can't be removed.  So we have to test for it here.  */
  if (breakpoint_here_p (aspace, pc) == permanent_breakpoint_here)
    {
      gdbarch_skip_permanent_breakpoint (gdbarch, regcache);
    }

But since gdbarch_skip_permanent_breakpoint already advanced the PC
manually, this ends up executing the instruction that is _after_ the
breakpoint instruction.  The user-visible result is that a single-step
steps two instructions.

The gdb.arch/i386-bp_permanent.exp test is actually ensuring that
that's indeed how things work.  It runs to an int3 instruction, does
"stepi", and checks that "leave" was executed with that "stepi".  Like
this:

 (gdb) b *0x0804848c
 Breakpoint 2 at 0x804848c
 (gdb) c
 Continuing.

 Breakpoint 2, 0x0804848c in standard ()
 (gdb) disassemble
 Dump of assembler code for function standard:
    0x08048488 <+0>:     push   %ebp
    0x08048489 <+1>:     mov    %esp,%ebp
    0x0804848b <+3>:     push   %edi
 => 0x0804848c <+4>:     int3
    0x0804848d <+5>:     leave
    0x0804848e <+6>:     ret
    0x0804848f <+7>:     nop
 (gdb) si
 0x0804848e in standard ()
 (gdb) disassemble
 Dump of assembler code for function standard:
    0x08048488 <+0>:     push   %ebp
    0x08048489 <+1>:     mov    %esp,%ebp
    0x0804848b <+3>:     push   %edi
    0x0804848c <+4>:     int3
    0x0804848d <+5>:     leave
 => 0x0804848e <+6>:     ret
    0x0804848f <+7>:     nop
 End of assembler dump.
 (gdb)

I would instead expect that a stepi at 0x0804848c stops at 0x0804848d,
_before_ the "leave" is executed.  This commit changes GDB this way.
Care is taken to make stepping into a signal handler when the step
starts at a permanent breakpoint instruction work correctly.

The patch adjusts gdb.arch/i386-bp_permanent.exp in this direction,
and also makes it work on x86_64 (currently it only works on i*86).

The patch also adds a new gdb.base/bp-permanent.exp test that
exercises many different code paths related to stepping permanent
breakpoints, including the stepping with signals cases.  The test uses
"hack/trick" to make it work on all (or most) platforms -- it doesn't
really hard code a breakpoint instruction.

Tested on x86_64 Fedora 20.

gdb/
2014-11-07  Pedro Alves  <palves@redhat.com>

	* infrun.c (resume): Clear the thread's 'stepped_breakpoint' flag.
	Rewrite stepping over a permanent breakpoint.
	(thread_still_needs_step_over, proceed): Don't set
	stepping_over_breakpoint for permanent breakpoints.
	(handle_signal_stop): Don't clear stepped_breakpoint.  Also pull
	single-step breakpoints out of the target on hardware step
	targets.
	(process_event_stop_test): If stepping a permanent breakpoint
	doesn't hit the step-resume breakpoint, delete the step-resume
	breakpoint.
	(switch_back_to_stepped_thread): Also check if the stepped thread
	has advanced already on hardware step targets.
	(currently_stepping): Return true if the thread stepped a
	breakpoint.

gdb/testsuite/
2014-11-07  Pedro Alves  <palves@redhat.com>

	* gdb.arch/i386-bp_permanent.c: New file.
	* gdb.arch/i386-bp_permanent.exp: Don't skip on x86_64.
	(srcfile): Set to i386-bp_permanent.c.
	(top level): Adjust to work in both 32-bit and 64-bit modes.  Test
	that stepi does not execute the 'leave' instruction, instead of
	testing it does execute.
	* gdb.base/bp-permanent.c: New file.
	* gdb.base/bp-permanent.exp: New file.
---
 gdb/infrun.c                                 | 114 ++++++++++-
 gdb/testsuite/gdb.arch/i386-bp_permanent.c   |  57 ++++++
 gdb/testsuite/gdb.arch/i386-bp_permanent.exp |  49 ++---
 gdb/testsuite/gdb.base/bp-permanent.c        | 128 +++++++++++++
 gdb/testsuite/gdb.base/bp-permanent.exp      | 275 +++++++++++++++++++++++++++
 5 files changed, 588 insertions(+), 35 deletions(-)
 create mode 100644 gdb/testsuite/gdb.arch/i386-bp_permanent.c
 create mode 100644 gdb/testsuite/gdb.base/bp-permanent.c
 create mode 100644 gdb/testsuite/gdb.base/bp-permanent.exp

diff --git a/gdb/infrun.c b/gdb/infrun.c
index f57920f..ff338f7 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2039,6 +2039,8 @@ resume (int step, enum gdb_signal sig)
      applies, it's the callers intention that counts.  */
   const int entry_step = step;
 
+  tp->stepped_breakpoint = 0;
+
   QUIT;
 
   if (current_inferior ()->waiting_for_vfork_done)
@@ -2075,7 +2077,81 @@ resume (int step, enum gdb_signal sig)
      breakpoints can't be removed.  So we have to test for it here.  */
   if (breakpoint_here_p (aspace, pc) == permanent_breakpoint_here)
     {
-      gdbarch_skip_permanent_breakpoint (gdbarch, regcache);
+      if (sig != GDB_SIGNAL_0)
+	{
+	  /* We have a signal to pass to the inferior.  The resume
+	     may, or may not take us to the signal handler.  If this
+	     is a step, we'll need to stop in the signal handler, if
+	     there's one, (if the target supports stepping into
+	     handlers), or in the next mainline instruction, if
+	     there's no handler.  If this is a continue, we need to be
+	     sure to run the handler with all breakpoints inserted.
+	     In all cases, set a breakpoint at the current address
+	     (where the handler returns to), and once that breakpoint
+	     is hit, resume skipping the permanent breakpoint.  If
+	     that breakpoint isn't hit, then we've stepped into the
+	     signal handler (or hit some other event).  We'll delete
+	     the step-resume breakpoint then.  */
+
+	  if (debug_infrun)
+	    fprintf_unfiltered (gdb_stdlog,
+				"infrun: resume: skipping permanent breakpoint, "
+				"deliver signal first\n");
+
+	  clear_step_over_info ();
+	  tp->control.trap_expected = 0;
+
+	  if (tp->control.step_resume_breakpoint == NULL)
+	    {
+	      /* Set a "high-priority" step-resume, as we don't want
+		 user breakpoints at PC to trigger (again) when this
+		 hits.  */
+	      insert_hp_step_resume_breakpoint_at_frame (get_current_frame ());
+	      gdb_assert (tp->control.step_resume_breakpoint->loc->permanent);
+
+	      tp->step_after_step_resume_breakpoint = step;
+	    }
+
+	  insert_breakpoints ();
+	}
+      else
+	{
+	  /* There's no signal to pass, we can go ahead and skip the
+	     permanent breakpoint manually.  */
+	  if (debug_infrun)
+	    fprintf_unfiltered (gdb_stdlog,
+				"infrun: resume: skipping permanent breakpoint\n");
+	  gdbarch_skip_permanent_breakpoint (gdbarch, regcache);
+	  /* Update pc to reflect the new address from which we will
+	     execute instructions.  */
+	  pc = regcache_read_pc (regcache);
+
+	  if (step)
+	    {
+	      /* We've already advanced the PC, so the stepping part
+		 is done.  Now we need to arrange for a trap to be
+		 reported to handle_inferior_event.  Set a breakpoint
+		 at the current PC, and run to it.  Don't update
+		 prev_pc, because if we end in
+		 switch_back_to_stepping, we want the "expected thread
+		 advanced also" branch to be taken.  IOW, we don't
+		 want this thread to step further from PC
+		 (overstep).  */
+	      insert_single_step_breakpoint (gdbarch, aspace, pc);
+	      insert_breakpoints ();
+
+	      tp->suspend.stop_signal = GDB_SIGNAL_0;
+	      /* We're continuing with all breakpoints inserted.  It's
+		 safe to let the target bypass signals.  */
+	      target_pass_signals ((int) GDB_SIGNAL_LAST, signal_pass);
+	      /* ... and safe to let other threads run, according to
+		 schedlock.  */
+	      resume_ptid = user_visible_resume_ptid (entry_step);
+	      target_resume (resume_ptid, 0, GDB_SIGNAL_0);
+	      discard_cleanups (old_cleanups);
+	      return;
+	    }
+	}
     }
 
   /* If we have a breakpoint to step over, make sure to do a single
@@ -2382,7 +2458,8 @@ thread_still_needs_step_over (struct thread_info *tp)
       struct regcache *regcache = get_thread_regcache (tp->ptid);
 
       if (breakpoint_here_p (get_regcache_aspace (regcache),
-			     regcache_read_pc (regcache)))
+			     regcache_read_pc (regcache))
+	  == ordinary_breakpoint_here)
 	return 1;
 
       tp->stepping_over_breakpoint = 0;
@@ -2498,7 +2575,8 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal, int step)
 
   if (addr == (CORE_ADDR) -1)
     {
-      if (pc == stop_pc && breakpoint_here_p (aspace, pc)
+      if (pc == stop_pc
+	  && breakpoint_here_p (aspace, pc) == ordinary_breakpoint_here
 	  && execution_direction != EXEC_REVERSE)
 	/* There is a breakpoint at the address we will resume at,
 	   step one instruction before inserting breakpoints so that
@@ -4190,7 +4268,6 @@ handle_signal_stop (struct execution_control_state *ecs)
   gdbarch = get_frame_arch (frame);
 
   /* Pull the single step breakpoints out of the target.  */
-  if (gdbarch_software_single_step_p (gdbarch))
     {
       if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP)
 	{
@@ -4280,7 +4357,6 @@ handle_signal_stop (struct execution_control_state *ecs)
       return;
     }
 
-  ecs->event_thread->stepped_breakpoint = 0;
   ecs->event_thread->stepping_over_breakpoint = 0;
   ecs->event_thread->stepping_over_watchpoint = 0;
   bpstat_clear (&ecs->event_thread->control.stop_bpstat);
@@ -4788,6 +4864,30 @@ process_event_stop_test (struct execution_control_state *ecs)
       break;
     }
 
+  /* If we stepped a permanent breakpoint and we had a high priority
+     step-resume breakpoint for the address we stepped, but we didn't
+     hit it, then we must have stepped into the signal handler.  The
+     step-resume was only necessary to catch the case of _not_
+     stepping into the handler, so delete it, and fall through to
+     checking whether the step finished.  */
+  if (ecs->event_thread->stepped_breakpoint)
+    {
+      struct breakpoint *sr_bp
+	= ecs->event_thread->control.step_resume_breakpoint;
+
+      if (sr_bp->loc->permanent
+	  && sr_bp->type == bp_hp_step_resume
+	  && sr_bp->loc->address == ecs->event_thread->prev_pc)
+	{
+	  if (debug_infrun)
+	    fprintf_unfiltered (gdb_stdlog,
+				"infrun: stepped permanent breakpoint, stopped in "
+				"handler\n");
+	  delete_step_resume_breakpoint (ecs->event_thread);
+	  ecs->event_thread->step_after_step_resume_breakpoint = 0;
+	}
+    }
+
   /* We come here if we hit a breakpoint but should not stop for it.
      Possibly we also were stepping and should stop for that.  So fall
      through and test for stepping.  But, if not stepping, do not
@@ -5552,8 +5652,7 @@ switch_back_to_stepped_thread (struct execution_control_state *ecs)
 	     breakpoint forward, one instruction at a time,
 	     overstepping.  */
 
-	  if (gdbarch_software_single_step_p (gdbarch)
-	      && stop_pc != tp->prev_pc)
+	  if (stop_pc != tp->prev_pc)
 	    {
 	      if (debug_infrun)
 		fprintf_unfiltered (gdb_stdlog,
@@ -5598,6 +5697,7 @@ currently_stepping (struct thread_info *tp)
   return ((tp->control.step_range_end
 	   && tp->control.step_resume_breakpoint == NULL)
 	  || tp->control.trap_expected
+	  || tp->stepped_breakpoint
 	  || bpstat_should_step ());
 }
 
diff --git a/gdb/testsuite/gdb.arch/i386-bp_permanent.c b/gdb/testsuite/gdb.arch/i386-bp_permanent.c
new file mode 100644
index 0000000..dc36d89
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/i386-bp_permanent.c
@@ -0,0 +1,57 @@
+/* Copyright (C) 2003-2014 Free Software Foundation, Inc.
+
+   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/>.  */
+
+#ifdef SYMBOL_PREFIX
+#define SYMBOL(str)	SYMBOL_PREFIX #str
+#else
+#define SYMBOL(str)	#str
+#endif
+
+int
+main (void)
+{
+  standard ();
+  return 0;
+}
+
+/* A normal prologue.  */
+
+#ifdef __x86_64__
+asm(".text\n"
+    "    .align 8\n"
+    SYMBOL (standard) ":\n"
+    "    push %rbp\n"
+    "    mov  %rsp, %rbp\n"
+    "    push %rdi\n"
+    "    int3\n"
+    "    leaveq\n"
+    "    retq\n");
+
+#else
+
+asm(".text\n"
+    "    .align 8\n"
+    "    .global " SYMBOL(standard) "\n"
+    SYMBOL (standard) ":\n"
+    "    pushl %ebp\n"
+    "    movl  %esp, %ebp\n"
+    "    pushl %edi\n"
+    "    int3\n"
+    "    leave\n"
+    "    ret\n");
+
+#endif
diff --git a/gdb/testsuite/gdb.arch/i386-bp_permanent.exp b/gdb/testsuite/gdb.arch/i386-bp_permanent.exp
index 6be1626..833b4ed 100644
--- a/gdb/testsuite/gdb.arch/i386-bp_permanent.exp
+++ b/gdb/testsuite/gdb.arch/i386-bp_permanent.exp
@@ -18,13 +18,13 @@
 
 # Test stepping over permanent breakpoints on i386.
 
-if { ![is_x86_like_target] } then {
+if { ![istarget "i?86-*-*"] && ![istarget "x86_64-*-*"] } then {
     verbose "Skipping skip over permanent breakpoint on i386 tests."
     return
 }
 
 set testfile "i386-bp_permanent"
-set srcfile i386-prologue.c
+set srcfile i386-bp_permanent.c
 set binfile ${objdir}/${subdir}/${testfile}
 
 # some targets have leading underscores on assembly symbols.
@@ -51,14 +51,12 @@ if ![runto_main] then {
   return -1
 }
 
-set function standard
+set function "standard"
 
 set retcode [gdb_test_multiple "disassemble $function" "Disassemble function '$function'" {
-    -re ".*($hex) <\\+0>.*($hex) <\\+4>.*($hex) <\\+5>.*($hex) <\\+6>.*$gdb_prompt $" {
-      set function_start $expect_out(1,string)
-      set address $expect_out(2,string)
-      set address1 $expect_out(3,string)
-      set address2 $expect_out(4,string)
+    -re "($hex) <\\+0>.*($hex) <\\+$decimal>.*int3.*($hex) <\\+$decimal>.*leave.*$gdb_prompt $" {
+      set address_bp $expect_out(2,string)
+      set address_after_bp $expect_out(3,string)
     }
 }]
 
@@ -67,30 +65,25 @@ if {$retcode != 0} {
   return -1
 }
 
-gdb_breakpoint "*$function_start"
+gdb_breakpoint "*$address_bp"
 
-gdb_breakpoint "*$address"
+gdb_test "continue" "Breakpoint .*, $address_bp in $function.*" \
+	 "stop at permanent breakpoint"
 
-gdb_test "continue" "Breakpoint .*, $function_start in $function.*" \
-	 "Stop at the '$function' start breakpoint (fetching esp)."
-
-# We want to fetch esp at the start of '$function' function to make sure
-# skip_permanent_breakpoint implementation really skips only the perm. 
-# breakpoint. If, for whatever reason, 'leave' instruction doesn't get
-# executed, esp will not have this value.
-set start_esp 0
-gdb_test_multiple "print \$esp" "Fetch esp value." {
+# We want to fetch the stack pointer at the start of '$function'
+# function to make sure the skip_permanent_breakpoint implementation
+# really skips only the permanent breakpoint.  If, for whatever
+# reason, the 'leave' instruction executes, the stack pointer will not
+# have this value.
+set start_sp 0
+gdb_test_multiple "print \$sp" "fetch stack pointer value" {
     -re "\\\$1.*($hex).*$gdb_prompt $" {
-      set start_esp $expect_out(1,string)
+      set start_sp $expect_out(1,string)
     }
 }
 
-gdb_test "continue" "Breakpoint .*, $address in $function.*" \
-	 "Stop at permanent breakpoint."
-
-gdb_test "stepi" "$address1|$address2 in $function.*" \
-	 "Single stepping past permanent breakpoint."
-
-gdb_test "print \$esp" ".*$start_esp.*" \
-	 "ESP value does not match - step_permanent_breakpoint wrong."
+gdb_test "stepi" "$address_after_bp in $function.*" \
+	 "single-step past permanent breakpoint"
 
+gdb_test "print \$sp" ".*$start_sp.*" \
+	 "stack pointer value matches"
diff --git a/gdb/testsuite/gdb.base/bp-permanent.c b/gdb/testsuite/gdb.base/bp-permanent.c
new file mode 100644
index 0000000..53b3777
--- /dev/null
+++ b/gdb/testsuite/gdb.base/bp-permanent.c
@@ -0,0 +1,128 @@
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+
+   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 <string.h>
+#ifdef SIGNALS
+#include <signal.h>
+#endif
+
+#define NOP asm("nop")
+
+/* Buffer holding the breakpoint instruction.  */
+unsigned char buffer[16];
+
+volatile unsigned char *addr_bp;
+volatile unsigned char *addr_after_bp;
+int counter = 0;
+
+void
+test (void)
+{
+  NOP;
+  NOP;
+  NOP;
+  NOP; /* write permanent bp here */
+  NOP; /* after permanent bp */
+  NOP;
+  NOP;
+  NOP;
+  NOP;
+  NOP;
+
+  counter++;
+}
+
+void
+setup (void)
+{
+  memcpy (buffer, (void *) addr_bp, addr_after_bp - addr_bp);
+}
+
+void
+test_basics (void)
+{
+  test (); /* for SIGTRAP */
+  test (); /* for breakpoint once */
+  test (); /* for breakpoint twice */
+  test (); /* for disabled bp SIGTRAP */
+  test (); /* for breakpoint thrice */
+}
+
+void
+test_next (void)
+{
+  test (); /* for next */
+  counter = 0; /* after next */
+}
+
+#ifdef SIGNALS
+
+static void
+test_signal_handler (int sig)
+{
+}
+
+void
+test_signal_with_handler (void)
+{
+  signal (SIGUSR1, test_signal_handler);
+  test ();
+}
+
+void
+test_signal_no_handler (void)
+{
+  signal (SIGUSR1, SIG_IGN);
+  test ();
+}
+
+static void
+test_signal_nested_handler ()
+{
+  test ();
+}
+
+void
+test_signal_nested_done (void)
+{
+}
+
+void
+test_signal_nested (void)
+{
+  counter = 0;
+  signal (SIGALRM, test_signal_nested_handler);
+  alarm (1);
+  test ();
+  test_signal_nested_done ();
+}
+
+#endif
+
+int
+main (void)
+{
+  setup ();
+  test_basics ();
+  test_next ();
+#ifdef SIGNALS
+  test_signal_nested ();
+  test_signal_with_handler ();
+  test_signal_no_handler ();
+#endif
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/bp-permanent.exp b/gdb/testsuite/gdb.base/bp-permanent.exp
new file mode 100644
index 0000000..95db700
--- /dev/null
+++ b/gdb/testsuite/gdb.base/bp-permanent.exp
@@ -0,0 +1,275 @@
+# Copyright (C) 2014 Free Software Foundation, Inc.
+
+# 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/>.
+
+# This file is part of the gdb testsuite.
+
+# Test stepping over permanent breakpoints.
+
+standard_testfile
+
+set options { debug }
+if { ![target_info exists gdb,nosignals] } {
+    lappend options "additional_flags=-DSIGNALS"
+}
+
+if {[build_executable "failed to prepare" $testfile $srcfile $options]} {
+    return -1
+}
+
+set line_bp [gdb_get_line_number "write permanent bp"]
+
+# The test proper.  ALWAYS_INSERTED indicates whether testing in
+# "breakpoint always-inserted" mode.  If SW_WATCHPOINT is true, set a
+# software watchpoint, which forces constantly single-stepping, and
+# exercises stepping the permanent breakpoint while delivering a
+# signal at the same time.
+
+proc test {always_inserted sw_watchpoint} {
+    global line_bp
+    global hex decimal
+    global gdb_prompt
+    global srcfile binfile
+
+    clean_restart $binfile
+
+    if ![runto_main] then {
+	return -1
+    }
+
+    gdb_test "set breakpoint always-inserted $always_inserted"
+
+    if {$sw_watchpoint} {
+	# Watching a convenience variable forces a software
+	# watchpoint.
+	gdb_test "watch \$dummy_convenience" "Watchpoint .*"
+    }
+
+    set address_bp ""
+    set address_after_bp ""
+
+    with_test_prefix "setup" {
+
+	# Set a breakpoint where we'll manually plant a permanent
+	# breakpoint.
+	set test "set probe breakpoint"
+	gdb_test_multiple "break $line_bp" $test {
+	    -re "Breakpoint .* at ($hex).*$gdb_prompt $" {
+		set address_bp $expect_out(1,string)
+		pass $test
+	    }
+	}
+	if {$address_bp == ""} {
+	    return
+	}
+
+	# Get the size of the instruction where the breakpoint will
+	# manually inserted.
+	set test "get size of instruction"
+	gdb_test_multiple "x/2i $address_bp" $test {
+	    -re ".*$hex <test\\+$decimal>:\[^\r\n\]+\r\n\[ \]+($hex).*\.\r\n$gdb_prompt $" {
+		set address_after_bp $expect_out(1,string)
+		pass $test
+	    }
+	}
+	if {$address_after_bp == ""} {
+	    return
+	}
+
+	# Write address range where the breakpoint is inserted to the
+	# corresponding variables in the inferior.
+	gdb_test "p /x addr_bp = $address_bp" " = $address_bp" \
+	    "write addr_bp"
+	gdb_test "p /x addr_after_bp = $address_after_bp" " = $address_after_bp" \
+	    "write addr_after_bp"
+
+	# Run the "setup" function in the inferior.  This memcpy's the
+	# breakpoint instruction to a buffer in the inferior.
+	gdb_test "next" "test.*" "next over setup"
+
+	delete_breakpoints
+
+	# We now have the breakpoint instruction stored in 'buffer'.  Poke it
+	# to memory manually.
+	set count [expr $address_after_bp - $address_bp]
+	for {set i 0} {$i < $count} {incr i} {
+	    gdb_test "p /x addr_bp\[$i\] = buffer\[$i\]" " = .*"
+	}
+    }
+
+    with_test_prefix "basics" {
+	# Run to the permanent breakpoint, just to make sure we've inserted it
+	# correctly.
+	gdb_test "continue" "Program received signal SIGTRAP.*" \
+	    "permanent breakpoint causes random signal"
+
+	# Now set a breakpoint on top, thus creating a permanent breakpoint.
+	gdb_breakpoint "$line_bp"
+
+	# Depending on whether this is a decr_pc_after_break arch, the PC will
+	# be either pointing at the permanent breakpoint address, or just
+	# after.  Set the GDB breakpoint on top, and continue, twice.  At
+	# least once, GDB will need to step-over the permanent breakpoint.
+
+	gdb_test "continue" "Breakpoint .*" "stop at permanent breakpoint"
+
+	gdb_test "p \$prev_counter = counter" " = $decimal"
+
+	gdb_test "continue" "Breakpoint .*" "stop at permanent breakpoint twice"
+
+	# Check that indeed the continue made progress, instead of re-trapping
+	# without advancing.
+	gdb_test "p counter - \$prev_counter" " = 1"
+
+	gdb_test "info breakpoints" \
+	    "breakpoint.*keep.*y.*$hex.*in test at .*$srcfile:$line_bp.*already hit 2 times.*" \
+	    "info breakpoints show enabled breakpoint"
+
+	gdb_test "disable \$bpnum"
+
+	gdb_test "commands\nset \$commands_ran = 1\nend" "" \
+	    "set breakpoint commands"
+
+	gdb_test "info breakpoints" \
+	    "breakpoint.*keep.*n.*$hex.*in test at .*$srcfile:$line_bp.*already hit 2 times.*" \
+	    "info breakpoints shows disabled breakpoint"
+
+	# Run to the permanent breakpoint again.  This time, since it's
+	# disabled, it should act as if we hadn't created it in the first
+	# place.  IOW, we should get a random signal, and, the breakpoint's
+	# command should not run.
+	gdb_test "continue" "Program received signal SIGTRAP.*" \
+	    "disabled permanent breakpoint doesn't explain stop"
+
+	gdb_test "info breakpoints" \
+	    "breakpoint.*keep.*n.*$hex.*in test at .*$srcfile:$line_bp.*already hit 2 times.*" \
+	    "info breakpoints still shows same number of hits"
+
+	gdb_test "print \$commands_ran" " = void" \
+	    "breakpoint commands didn't run"
+
+	# Reenable the breakpoint, and check that it gets hit and accounted
+	# for this time.
+	gdb_test "enable \$bpnum" "" "reenable breakpoint"
+
+	gdb_test "continue" "Breakpoint .*" \
+	    "stop at permanent breakpoint thrice"
+
+	gdb_test "info breakpoints" \
+	    "breakpoint.*keep.*y.*$hex.*in test at .*$srcfile:$line_bp.*already hit 3 times.*" \
+	    "info breakpoints shows one more hit"
+
+	gdb_test "print \$commands_ran" " = 1" "breakpoint commands ran"
+
+	# Check that stepi advances only past the permanent breakpoint, and
+	# not a single instruction more.
+	gdb_test "stepi" "after permanent bp .*" \
+	    "single-step past permanent breakpoint"
+    }
+
+    with_test_prefix "next trips on permanent bp" {
+	delete_breakpoints
+
+	gdb_breakpoint "test_next"
+	gdb_continue_to_breakpoint "test_next"
+
+	gdb_breakpoint "$line_bp"
+	gdb_test "condition \$bpnum 0"
+
+	gdb_test "next" "after next .*"
+    }
+
+    if ![target_info exists gdb,nosignals] {
+
+	with_test_prefix "continue trips on nested permanent bp" {
+	    delete_breakpoints
+
+	    gdb_breakpoint "test_signal_nested"
+	    gdb_continue_to_breakpoint "test_signal_nested"
+
+	    gdb_breakpoint "$line_bp"
+	    gdb_continue_to_breakpoint "permanent bp"
+	    gdb_test "condition \$bpnum 0"
+
+	    # Let SIGALRM trigger.
+	    sleep 2
+
+	    # We're now stopped at a permanent breakpoint, with a
+	    # signal pending.
+	    gdb_breakpoint "test_signal_nested_done"
+	    gdb_continue_to_breakpoint "test_signal_nested_done"
+
+	    # Ensure that the handler did run.  There's one call to
+	    # test in the mainline code, and another in the signal
+	    # handler.
+	    gdb_test "p counter" " = 2"
+	}
+
+	if [can_single_step_to_signal_handler] {
+
+	    with_test_prefix "stepi signal with handler" {
+		delete_breakpoints
+
+		gdb_breakpoint "test_signal_with_handler"
+		gdb_continue_to_breakpoint "test_signal_with_handler"
+
+		gdb_breakpoint "$line_bp"
+
+		gdb_test "continue" "Breakpoint .*" "stop at permanent breakpoint"
+
+		gdb_test "queue-signal SIGUSR1"
+
+		set test "single-step to handler"
+		gdb_test_multiple "stepi" $test {
+		    -re "Program received signal SIGTRAP.*$gdb_prompt $" {
+			fail $test
+		    }
+		    -re "handler .*$gdb_prompt $" {
+			pass $test
+		    }
+		}
+
+		# Check that the mainline PC points at the permanent
+		# breakpoint.
+		gdb_test "up 2" "test .*" "up to mainline code"
+
+		gdb_test "p /x \$pc" " = $address_bp" \
+		    "mainline pc points at permanent breakpoint"
+
+		gdb_test "continue" "Breakpoint .*" \
+		    "stop at permanent breakpoint, out of handler"
+	    }
+
+	    with_test_prefix "stepi signal with no handler" {
+		gdb_breakpoint "test_signal_no_handler"
+		gdb_continue_to_breakpoint "test_signal_no_handler"
+
+		gdb_test "continue" "Breakpoint .*" "stop at permanent breakpoint"
+
+		gdb_test "queue-signal SIGUSR1"
+
+		gdb_test "stepi" "after permanent bp .*" \
+		    "single-step past permanent breakpoint"
+	    }
+	}
+    }
+}
+
+foreach always_inserted {off on} {
+    foreach sw_watchpoint {0 1} {
+	with_test_prefix "always_inserted=$always_inserted, sw_watchpoint=$sw_watchpoint" {
+	    test $always_inserted $sw_watchpoint
+	}
+    }
+}
-- 
1.9.3



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]