mirror of
https://github.com/darlinghq/darling-gdb.git
synced 2025-01-17 06:22:49 +00:00
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:
parent
1a853c5224
commit
af48d08f97
@ -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.
|
||||
|
173
gdb/infrun.c
173
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,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 ());
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
57
gdb/testsuite/gdb.arch/i386-bp_permanent.c
Normal file
57
gdb/testsuite/gdb.arch/i386-bp_permanent.c
Normal 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
|
@ -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"
|
||||
|
128
gdb/testsuite/gdb.base/bp-permanent.c
Normal file
128
gdb/testsuite/gdb.base/bp-permanent.c
Normal 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;
|
||||
}
|
275
gdb/testsuite/gdb.base/bp-permanent.exp
Normal file
275
gdb/testsuite/gdb.base/bp-permanent.exp
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user