fix skipping permanent breakpoints

The gdb.arch/i386-bp_permanent.exp test is currently failing an
assertion 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.

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)

One 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, native and gdbserver.

gdb/
2014-11-12  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-12  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.
This commit is contained in:
Pedro Alves 2014-11-12 10:10:49 +00:00
parent 1a853c5224
commit af48d08f97
7 changed files with 644 additions and 66 deletions

View File

@ -1,3 +1,20 @@
2014-11-12 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.
2014-11-12 Pedro Alves <palves@redhat.com>
Mark locations as permanent, not the whole breakpoint.

View File

@ -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,50 +4268,46 @@ 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)
{
if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP)
struct regcache *regcache;
struct address_space *aspace;
CORE_ADDR pc;
regcache = get_thread_regcache (ecs->ptid);
aspace = get_regcache_aspace (regcache);
pc = regcache_read_pc (regcache);
/* However, before doing so, if this single-step breakpoint was
actually for another thread, set this thread up for moving
past it. */
if (!thread_has_single_step_breakpoint_here (ecs->event_thread,
aspace, pc))
{
struct regcache *regcache;
struct address_space *aspace;
CORE_ADDR pc;
regcache = get_thread_regcache (ecs->ptid);
aspace = get_regcache_aspace (regcache);
pc = regcache_read_pc (regcache);
/* However, before doing so, if this single-step breakpoint was
actually for another thread, set this thread up for moving
past it. */
if (!thread_has_single_step_breakpoint_here (ecs->event_thread,
aspace, pc))
{
if (single_step_breakpoint_inserted_here_p (aspace, pc))
{
if (debug_infrun)
{
fprintf_unfiltered (gdb_stdlog,
"infrun: [%s] hit another thread's "
"single-step breakpoint\n",
target_pid_to_str (ecs->ptid));
}
ecs->hit_singlestep_breakpoint = 1;
}
}
else
if (single_step_breakpoint_inserted_here_p (aspace, pc))
{
if (debug_infrun)
{
fprintf_unfiltered (gdb_stdlog,
"infrun: [%s] hit its "
"infrun: [%s] hit another thread's "
"single-step breakpoint\n",
target_pid_to_str (ecs->ptid));
}
ecs->hit_singlestep_breakpoint = 1;
}
}
else
{
if (debug_infrun)
{
fprintf_unfiltered (gdb_stdlog,
"infrun: [%s] hit its "
"single-step breakpoint\n",
target_pid_to_str (ecs->ptid));
}
}
delete_just_stopped_threads_single_step_breakpoints ();
}
delete_just_stopped_threads_single_step_breakpoints ();
if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP
&& ecs->event_thread->control.trap_expected
@ -4280,7 +4354,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 +4861,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 +5649,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 +5694,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 ());
}

View File

@ -1,3 +1,14 @@
2014-11-12 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.
2014-11-10 Doug Evans <xdje42@gmail.com>
PR symtab/17564

View File

@ -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

View File

@ -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"

View File

@ -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;
}

View File

@ -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
}
}
}