From patchwork Tue Nov 4 19:03:10 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pedro Alves X-Patchwork-Id: 3578 Received: (qmail 5620 invoked by alias); 4 Nov 2014 19:03:27 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 5434 invoked by uid 89); 4 Nov 2014 19:03:25 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-1.5 required=5.0 tests=AWL, BAYES_00, KAM_STOCKGEN, RP_MATCHES_RCVD, SPF_HELO_PASS, SPF_PASS autolearn=no version=3.3.2 X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Tue, 04 Nov 2014 19:03:20 +0000 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id sA4J3HNY030504 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL) for ; Tue, 4 Nov 2014 14:03:18 -0500 Received: from brno.lan (ovpn01.gateway.prod.ext.ams2.redhat.com [10.39.146.11]) by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id sA4J3Bm5026910 for ; Tue, 4 Nov 2014 14:03:16 -0500 From: Pedro Alves To: gdb-patches@sourceware.org Subject: [PATCH 3/3] fix skipping permanent breakpoints Date: Tue, 4 Nov 2014 19:03:10 +0000 Message-Id: <1415127790-15091-4-git-send-email-palves@redhat.com> In-Reply-To: <1415127790-15091-1-git-send-email-palves@redhat.com> References: <1415127790-15091-1-git-send-email-palves@redhat.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 * 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 * 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 . */ + +#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 . */ + +#include +#ifdef SIGNALS +#include +#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 . + +# 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 :\[^\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 + } + } +}