This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[PATCH 3/3] fix skipping permanent breakpoints
- From: Pedro Alves <palves at redhat dot com>
- To: gdb-patches at sourceware dot org
- Date: Tue, 4 Nov 2014 19:03:10 +0000
- Subject: [PATCH 3/3] fix skipping permanent breakpoints
- Authentication-results: sourceware.org; auth=none
- References: <1415127790-15091-1-git-send-email-palves at redhat dot com>
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-04 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 step
stepping_over_breakpoint for permanent breakpoints.
(handle_signal_stop): Don't clear stepped_breakpoint.
(process_event_stop_test): If stepping a permanent breakpoint
doesn't hit the step-resume breakpoint, delete the step-resume
breakpoint.
(currently_stepping): Return true if the thread stepped a
breakpoint.
(insert_step_resume_breakpoint_at_frame_1): New function, factored
out from ...
(insert_hp_step_resume_breakpoint_at_frame) ... this.
(insert_step_resume_breakpoint_at_frame): New function.
gdb/testsuite/
2014-11-04 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 | 149 +++++++++++++--
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 | 276 +++++++++++++++++++++++++++
5 files changed, 615 insertions(+), 44 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 4c4f03b..24f2603 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2013,6 +2013,8 @@ user_visible_resume_ptid (int step)
return resume_ptid;
}
+static void insert_step_resume_breakpoint_at_frame (struct frame_info *return_frame);
+
/* Resume the inferior, but allow a QUIT. This is useful if the user
wants to interrupt some lengthy single-stepping operation
(for child processes, the SIGINT goes to the inferior, and so
@@ -2039,6 +2041,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 +2079,76 @@ 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 single-step 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 be 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. 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));
+ 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. */
+ if (step)
+ {
+ /* If we already have a step-resume breakpoint set, then
+ we can just continue until that one is hit. */
+ if (tp->control.step_resume_breakpoint == NULL)
+ insert_step_resume_breakpoint_at_frame (get_current_frame ());
+ step = 0;
+ }
+
+ insert_breakpoints ();
+ }
}
/* If we have a breakpoint to step over, make sure to do a single
@@ -2382,7 +2455,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 +2572,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
@@ -4280,7 +4355,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 +4862,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 _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
@@ -5604,6 +5702,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 ());
}
@@ -5758,6 +5857,28 @@ insert_step_resume_breakpoint_at_sal (struct gdbarch *gdbarch,
bp_step_resume);
}
+/* Insert a "step-resume breakpoint" of type SR_TYPE at FRAME.pc. */
+
+static void
+insert_step_resume_breakpoint_at_frame_1 (struct frame_info *frame,
+ enum bptype sr_type)
+{
+ struct symtab_and_line sr_sal;
+ struct gdbarch *gdbarch;
+
+ gdb_assert (frame != NULL);
+ init_sal (&sr_sal); /* initialize to zeros */
+
+ gdbarch = get_frame_arch (frame);
+ sr_sal.pc = gdbarch_addr_bits_remove (gdbarch, get_frame_pc (frame));
+ sr_sal.section = find_pc_overlay (sr_sal.pc);
+ sr_sal.pspace = get_frame_program_space (frame);
+
+ insert_step_resume_breakpoint_at_sal_1 (gdbarch, sr_sal,
+ get_stack_frame_id (frame),
+ sr_type);
+}
+
/* Insert a "high-priority step-resume breakpoint" at RETURN_FRAME.pc.
This is used to skip a potential signal handler.
@@ -5768,20 +5889,16 @@ insert_step_resume_breakpoint_at_sal (struct gdbarch *gdbarch,
static void
insert_hp_step_resume_breakpoint_at_frame (struct frame_info *return_frame)
{
- struct symtab_and_line sr_sal;
- struct gdbarch *gdbarch;
-
- gdb_assert (return_frame != NULL);
- init_sal (&sr_sal); /* initialize to zeros */
+ insert_step_resume_breakpoint_at_frame_1 (return_frame, bp_hp_step_resume);
+}
- gdbarch = get_frame_arch (return_frame);
- sr_sal.pc = gdbarch_addr_bits_remove (gdbarch, get_frame_pc (return_frame));
- sr_sal.section = find_pc_overlay (sr_sal.pc);
- sr_sal.pspace = get_frame_program_space (return_frame);
+/* Insert a regular "step-resume breakpoint" at FRAME.pc.
+ This is used to skip a permanent breakpoint. */
- insert_step_resume_breakpoint_at_sal_1 (gdbarch, sr_sal,
- get_stack_frame_id (return_frame),
- bp_hp_step_resume);
+static void
+insert_step_resume_breakpoint_at_frame (struct frame_info *frame)
+{
+ insert_step_resume_breakpoint_at_frame_1 (frame, bp_step_resume);
}
/* Insert a "step-resume breakpoint" at the previous frame's PC. This
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..62bf34f
--- /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_with_handler ();
+ test_signal_no_handler ();
+ test_signal_nested ();
+#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..2bb1ef1
--- /dev/null
+++ b/gdb/testsuite/gdb.base/bp-permanent.exp
@@ -0,0 +1,276 @@
+# 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 "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"
+ }
+
+ 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. Continuing first skips the permanent
+ # breakpoint, sets a step-resume at the following
+ # instruction, and delivers the signal. The handler trips
+ # on the permanent breakpoint again, and this time, no
+ # step-resume should be set, as we already had one.
+ 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"
+ }
+ }
+}
+
+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