aboutsummaryrefslogtreecommitdiff
path: root/gdb
diff options
context:
space:
mode:
Diffstat (limited to 'gdb')
-rw-r--r--gdb/ChangeLog31
-rw-r--r--gdb/breakpoint.c106
-rw-r--r--gdb/breakpoint.h19
-rw-r--r--gdb/dummy-frame.c51
-rw-r--r--gdb/dummy-frame.h2
-rw-r--r--gdb/gdbthread.h4
-rw-r--r--gdb/infcall.c13
-rw-r--r--gdb/infrun.c22
-rw-r--r--gdb/testsuite/ChangeLog8
-rw-r--r--gdb/testsuite/gdb.base/call-signal-resume.exp12
-rw-r--r--gdb/testsuite/gdb.base/stale-infcall.c63
-rw-r--r--gdb/testsuite/gdb.base/stale-infcall.exp57
12 files changed, 367 insertions, 21 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 86c5093..ee6dfc3 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,34 @@
+2012-06-18 Jan Kratochvil <jan.kratochvil@redhat.com>
+
+ Remove stale dummy frames.
+ * breakpoint.c: Include dummy-frame.h.
+ (longjmp_breakpoint_ops): New variable.
+ (update_breakpoints_after_exec, breakpoint_init_inferior): Delete also
+ bp_longjmp_call_dummy.
+ (bpstat_what, bptype_string, print_one_breakpoint_location)
+ (init_bp_location): Support bp_longjmp_call_dummy.
+ (set_longjmp_breakpoint): Use longjmp_breakpoint_ops. Comment why.
+ (set_longjmp_breakpoint_for_call_dummy)
+ (check_longjmp_breakpoint_for_call_dummy, longjmp_bkpt_dtor): New
+ functions.
+ (initialize_breakpoint_ops): Initialize longjmp_breakpoint_ops.
+ * breakpoint.h (enum bptype): New item bp_longjmp_call_dummy. Delete
+ FIXME comment and extend the other comment for bp_call_dummy.
+ (set_longjmp_breakpoint_for_call_dummy)
+ (check_longjmp_breakpoint_for_call_dummy): New declarations.
+ * dummy-frame.c: Include gdbthread.h.
+ (pop_dummy_frame_bpt): New function.
+ (pop_dummy_frame): Call pop_dummy_frame_bpt.
+ (dummy_frame_discard): New function.
+ (cleanup_dummy_frames): Update the comment about longjmps.
+ * dummy-frame.h (dummy_frame_discard): New declaration.
+ * gdbthread.h (struct thread_info): Extend initiating_frame comment.
+ * infcall.c (call_function_by_hand): New variable longjmp_b. Call
+ set_longjmp_breakpoint_for_call_dummy. Chain its breakpoints with BPT.
+ * infrun.c (handle_inferior_event) <BPSTAT_WHAT_CLEAR_LONGJMP_RESUME>:
+ Add case 4 comment. Call check_longjmp_breakpoint_for_call_dummy and
+ keep_going if IS_LONGJMP and there is no other reason to stop.
+
2012-06-18 Greta Yorsh <Greta.Yorsh@arm.com>
* remote-sim.c (sim_command_completer): Initialize
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 82265cc..12ab271 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -68,6 +68,7 @@
#include "skip.h"
#include "gdb_regex.h"
#include "ax-gdb.h"
+#include "dummy-frame.h"
/* readline include files */
#include "readline/readline.h"
@@ -287,6 +288,9 @@ static struct breakpoint_ops internal_breakpoint_ops;
/* Momentary breakpoints class type. */
static struct breakpoint_ops momentary_breakpoint_ops;
+/* Momentary breakpoints for bp_longjmp and bp_exception class type. */
+static struct breakpoint_ops longjmp_breakpoint_ops;
+
/* The breakpoint_ops structure to be used in regular user created
breakpoints. */
struct breakpoint_ops bkpt_breakpoint_ops;
@@ -3204,6 +3208,7 @@ update_breakpoints_after_exec (void)
/* Longjmp and longjmp-resume breakpoints are also meaningless
after an exec. */
if (b->type == bp_longjmp || b->type == bp_longjmp_resume
+ || b->type == bp_longjmp_call_dummy
|| b->type == bp_exception || b->type == bp_exception_resume)
{
delete_breakpoint (b);
@@ -3495,6 +3500,7 @@ breakpoint_init_inferior (enum inf_context context)
switch (b->type)
{
case bp_call_dummy:
+ case bp_longjmp_call_dummy:
/* If the call dummy breakpoint is at the entry point it will
cause problems when the inferior is rerun, so we better get
@@ -5154,9 +5160,10 @@ bpstat_what (bpstat bs_head)
}
break;
case bp_longjmp:
+ case bp_longjmp_call_dummy:
case bp_exception:
this_action = BPSTAT_WHAT_SET_LONGJMP_RESUME;
- retval.is_longjmp = bptype == bp_longjmp;
+ retval.is_longjmp = bptype != bp_exception;
break;
case bp_longjmp_resume:
case bp_exception_resume:
@@ -5489,6 +5496,7 @@ bptype_string (enum bptype type)
{bp_access_watchpoint, "acc watchpoint"},
{bp_longjmp, "longjmp"},
{bp_longjmp_resume, "longjmp resume"},
+ {bp_longjmp_call_dummy, "longjmp for call dummy"},
{bp_exception, "exception"},
{bp_exception_resume, "exception resume"},
{bp_step_resume, "step resume"},
@@ -5631,6 +5639,7 @@ print_one_breakpoint_location (struct breakpoint *b,
case bp_finish:
case bp_longjmp:
case bp_longjmp_resume:
+ case bp_longjmp_call_dummy:
case bp_exception:
case bp_exception_resume:
case bp_step_resume:
@@ -6494,6 +6503,7 @@ init_bp_location (struct bp_location *loc, const struct bp_location_ops *ops,
case bp_finish:
case bp_longjmp:
case bp_longjmp_resume:
+ case bp_longjmp_call_dummy:
case bp_exception:
case bp_exception_resume:
case bp_step_resume:
@@ -6797,8 +6807,10 @@ set_longjmp_breakpoint (struct thread_info *tp, struct frame_id frame)
enum bptype type = b->type == bp_longjmp_master ? bp_longjmp : bp_exception;
struct breakpoint *clone;
+ /* longjmp_breakpoint_ops ensures INITIATING_FRAME is cleared again
+ after their removal. */
clone = momentary_breakpoint_from_master (b, type,
- &momentary_breakpoint_ops);
+ &longjmp_breakpoint_ops);
clone->thread = thread;
}
@@ -6832,6 +6844,75 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
}
}
+/* Place breakpoints of type bp_longjmp_call_dummy to catch longjmp for
+ INFERIOR_PTID thread. Chain them all by RELATED_BREAKPOINT and return
+ pointer to any of them. Return NULL if this system cannot place longjmp
+ breakpoints. */
+
+struct breakpoint *
+set_longjmp_breakpoint_for_call_dummy (void)
+{
+ struct breakpoint *b, *retval = NULL;
+
+ ALL_BREAKPOINTS (b)
+ if (b->pspace == current_program_space && b->type == bp_longjmp_master)
+ {
+ struct breakpoint *new_b;
+
+ new_b = momentary_breakpoint_from_master (b, bp_longjmp_call_dummy,
+ &momentary_breakpoint_ops);
+ new_b->thread = pid_to_thread_id (inferior_ptid);
+
+ /* Link NEW_B into the chain of RETVAL breakpoints. */
+
+ gdb_assert (new_b->related_breakpoint == new_b);
+ if (retval == NULL)
+ retval = new_b;
+ new_b->related_breakpoint = retval;
+ while (retval->related_breakpoint != new_b->related_breakpoint)
+ retval = retval->related_breakpoint;
+ retval->related_breakpoint = new_b;
+ }
+
+ return retval;
+}
+
+/* Verify all existing dummy frames and their associated breakpoints for
+ THREAD. Remove those which can no longer be found in the current frame
+ stack.
+
+ You should call this function only at places where it is safe to currently
+ unwind the whole stack. Failed stack unwind would discard live dummy
+ frames. */
+
+void
+check_longjmp_breakpoint_for_call_dummy (int thread)
+{
+ struct breakpoint *b, *b_tmp;
+
+ ALL_BREAKPOINTS_SAFE (b, b_tmp)
+ if (b->type == bp_longjmp_call_dummy && b->thread == thread)
+ {
+ struct breakpoint *dummy_b = b->related_breakpoint;
+
+ while (dummy_b != b && dummy_b->type != bp_call_dummy)
+ dummy_b = dummy_b->related_breakpoint;
+ if (dummy_b->type != bp_call_dummy
+ || frame_find_by_id (dummy_b->frame_id) != NULL)
+ continue;
+
+ dummy_frame_discard (dummy_b->frame_id);
+
+ while (b->related_breakpoint != b)
+ {
+ if (b_tmp == b->related_breakpoint)
+ b_tmp = b->related_breakpoint->next;
+ delete_breakpoint (b->related_breakpoint);
+ }
+ delete_breakpoint (b);
+ }
+}
+
void
enable_overlay_breakpoints (void)
{
@@ -12821,6 +12902,22 @@ momentary_bkpt_print_mention (struct breakpoint *b)
/* Nothing to mention. These breakpoints are internal. */
}
+/* Ensure INITIATING_FRAME is cleared when no such breakpoint exists.
+
+ It gets cleared already on the removal of the first one of such placed
+ breakpoints. This is OK as they get all removed altogether. */
+
+static void
+longjmp_bkpt_dtor (struct breakpoint *self)
+{
+ struct thread_info *tp = find_thread_id (self->thread);
+
+ if (tp)
+ tp->initiating_frame = null_frame_id;
+
+ momentary_breakpoint_ops.dtor (self);
+}
+
/* Specific methods for probe breakpoints. */
static int
@@ -15409,6 +15506,11 @@ initialize_breakpoint_ops (void)
ops->print_it = momentary_bkpt_print_it;
ops->print_mention = momentary_bkpt_print_mention;
+ /* Momentary breakpoints for bp_longjmp and bp_exception. */
+ ops = &longjmp_breakpoint_ops;
+ *ops = momentary_breakpoint_ops;
+ ops->dtor = longjmp_bkpt_dtor;
+
/* Probe breakpoints. */
ops = &bkpt_probe_breakpoint_ops;
*ops = bkpt_breakpoint_ops;
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 2ad80d6..68a9688 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -65,6 +65,12 @@ enum bptype
bp_longjmp, /* secret breakpoint to find longjmp() */
bp_longjmp_resume, /* secret breakpoint to escape longjmp() */
+ /* Breakpoint placed to the same location(s) like bp_longjmp but used to
+ protect against stale DUMMY_FRAME. Multiple bp_longjmp_call_dummy and
+ one bp_call_dummy are chained together by related_breakpoint for each
+ DUMMY_FRAME. */
+ bp_longjmp_call_dummy,
+
/* An internal breakpoint that is installed on the unwinder's
debug hook. */
bp_exception,
@@ -94,14 +100,8 @@ enum bptype
3) It can never be disabled. */
bp_watchpoint_scope,
- /* The breakpoint at the end of a call dummy. */
- /* FIXME: What if the function we are calling longjmp()s out of
- the call, or the user gets out with the "return" command? We
- currently have no way of cleaning up the breakpoint in these
- (obscure) situations. (Probably can solve this by noticing
- longjmp, "return", etc., it's similar to noticing when a
- watchpoint on a local variable goes out of scope (with hardware
- support for watchpoints)). */
+ /* The breakpoint at the end of a call dummy. See bp_longjmp_call_dummy it
+ is chained with by related_breakpoint. */
bp_call_dummy,
/* A breakpoint set on std::terminate, that is used to catch
@@ -1287,6 +1287,9 @@ extern void delete_longjmp_breakpoint (int thread);
/* Mark all longjmp breakpoints from THREAD for later deletion. */
extern void delete_longjmp_breakpoint_at_next_stop (int thread);
+extern struct breakpoint *set_longjmp_breakpoint_for_call_dummy (void);
+extern void check_longjmp_breakpoint_for_call_dummy (int thread);
+
extern void enable_overlay_breakpoints (void);
extern void disable_overlay_breakpoints (void);
diff --git a/gdb/dummy-frame.c b/gdb/dummy-frame.c
index 4a42a76..b8a8c86 100644
--- a/gdb/dummy-frame.c
+++ b/gdb/dummy-frame.c
@@ -29,6 +29,7 @@
#include "gdbcmd.h"
#include "gdb_string.h"
#include "observer.h"
+#include "gdbthread.h"
/* Dummy frame. This saves the processor state just prior to setting
up the inferior function call. Older targets save the registers
@@ -108,19 +109,44 @@ remove_dummy_frame (struct dummy_frame **dummy_ptr)
xfree (dummy);
}
+/* Delete any breakpoint B which is a momentary breakpoint for return from
+ inferior call matching DUMMY_VOIDP. */
+
+static int
+pop_dummy_frame_bpt (struct breakpoint *b, void *dummy_voidp)
+{
+ struct dummy_frame *dummy = dummy_voidp;
+
+ if (b->thread == pid_to_thread_id (inferior_ptid)
+ && b->disposition == disp_del && frame_id_eq (b->frame_id, dummy->id))
+ {
+ while (b->related_breakpoint != b)
+ delete_breakpoint (b->related_breakpoint);
+
+ delete_breakpoint (b);
+
+ /* Stop the traversal. */
+ return 1;
+ }
+
+ /* Continue the traversal. */
+ return 0;
+}
+
/* Pop *DUMMY_PTR, restoring program state to that before the
frame was created. */
static void
pop_dummy_frame (struct dummy_frame **dummy_ptr)
{
- struct dummy_frame *dummy;
+ struct dummy_frame *dummy = *dummy_ptr;
+
+ restore_infcall_suspend_state (dummy->caller_state);
- restore_infcall_suspend_state ((*dummy_ptr)->caller_state);
+ iterate_over_breakpoints (pop_dummy_frame_bpt, dummy);
/* restore_infcall_control_state frees inf_state,
all that remains is to pop *dummy_ptr. */
- dummy = *dummy_ptr;
*dummy_ptr = dummy->next;
xfree (dummy);
@@ -166,9 +192,22 @@ dummy_frame_pop (struct frame_id dummy_id)
pop_dummy_frame (dp);
}
-/* There may be stale dummy frames, perhaps left over from when a longjump took
- us out of a function that was called by the debugger. Clean them up at
- least once whenever we start a new inferior. */
+/* Drop dummy frame DUMMY_ID. Do nothing if it is not found. Do not restore
+ its state into inferior, just free its memory. */
+
+void
+dummy_frame_discard (struct frame_id dummy_id)
+{
+ struct dummy_frame **dp;
+
+ dp = lookup_dummy_frame (dummy_id);
+ if (dp)
+ remove_dummy_frame (dp);
+}
+
+/* There may be stale dummy frames, perhaps left over from when an uncaught
+ longjmp took us out of a function that was called by the debugger. Clean
+ them up at least once whenever we start a new inferior. */
static void
cleanup_dummy_frames (struct target_ops *target, int from_tty)
diff --git a/gdb/dummy-frame.h b/gdb/dummy-frame.h
index a921e92..1b3979d 100644
--- a/gdb/dummy-frame.h
+++ b/gdb/dummy-frame.h
@@ -52,6 +52,8 @@ extern void dummy_frame_push (struct infcall_suspend_state *caller_state,
extern void dummy_frame_pop (struct frame_id dummy_id);
+extern void dummy_frame_discard (struct frame_id dummy_id);
+
/* If the PC falls in a dummy frame, return a dummy frame
unwinder. */
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 2512357..7cd66b6 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -216,7 +216,9 @@ struct thread_info
int stop_requested;
/* The initiating frame of a nexting operation, used for deciding
- which exceptions to intercept. */
+ which exceptions to intercept. If it is null_frame_id no
+ bp_longjmp or bp_exception but longjmp has been caught just for
+ bp_longjmp_call_dummy. */
struct frame_id initiating_frame;
/* Private data used by the target vector implementation. */
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 20a2c2b..51cd118 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -744,7 +744,7 @@ call_function_by_hand (struct value *function, int nargs, struct value **args)
inferior. That way it breaks when it returns. */
{
- struct breakpoint *bpt;
+ struct breakpoint *bpt, *longjmp_b;
struct symtab_and_line sal;
init_sal (&sal); /* initialize to zeroes */
@@ -760,6 +760,17 @@ call_function_by_hand (struct value *function, int nargs, struct value **args)
frame = NULL;
bpt->disposition = disp_del;
+ gdb_assert (bpt->related_breakpoint == bpt);
+
+ longjmp_b = set_longjmp_breakpoint_for_call_dummy ();
+ if (longjmp_b)
+ {
+ /* Link BPT into the chain of LONGJMP_B. */
+ bpt->related_breakpoint = longjmp_b;
+ while (longjmp_b->related_breakpoint != bpt->related_breakpoint)
+ longjmp_b = longjmp_b->related_breakpoint;
+ longjmp_b->related_breakpoint = bpt;
+ }
}
/* Create a breakpoint in std::terminate.
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 95a3ae9..53db335 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4445,18 +4445,34 @@ process_event_stop_test:
3. The initiating frame exists and is different from the
current frame. This means the exception or longjmp has
been caught beneath the initiating frame, so keep
- going. */
+ going.
+
+ 4. longjmp breakpoint has been placed just to protect
+ against stale dummy frames and user is not interested in
+ stopping around longjmps. */
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog,
"infrun: BPSTAT_WHAT_CLEAR_LONGJMP_RESUME\n");
- init_frame = frame_find_by_id (ecs->event_thread->initiating_frame);
-
gdb_assert (ecs->event_thread->control.exception_resume_breakpoint
!= NULL);
delete_exception_resume_breakpoint (ecs->event_thread);
+ if (what.is_longjmp)
+ {
+ check_longjmp_breakpoint_for_call_dummy (ecs->event_thread->num);
+
+ if (!frame_id_p (ecs->event_thread->initiating_frame))
+ {
+ /* Case 4. */
+ keep_going (ecs);
+ return;
+ }
+ }
+
+ init_frame = frame_find_by_id (ecs->event_thread->initiating_frame);
+
if (init_frame)
{
struct frame_id current_id
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index 2b55c3e..be5f0f6 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,11 @@
+2012-06-18 Jan Kratochvil <jan.kratochvil@redhat.com>
+
+ Remove stale dummy frames.
+ * gdb.base/call-signal-resume.exp (maintenance print dummy-frames)
+ (maintenance info breakpoints): New tests.
+ * gdb.base/stale-infcall.c: New file.
+ * gdb.base/stale-infcall.exp: New file.
+
2012-06-17 Jan Kratochvil <jan.kratochvil@redhat.com>
* gdb.arch/amd64-entry-value-param.S: New file.
diff --git a/gdb/testsuite/gdb.base/call-signal-resume.exp b/gdb/testsuite/gdb.base/call-signal-resume.exp
index d383f5c..2baa3f6 100644
--- a/gdb/testsuite/gdb.base/call-signal-resume.exp
+++ b/gdb/testsuite/gdb.base/call-signal-resume.exp
@@ -101,6 +101,18 @@ gdb_test "frame $frame_number" ".*"
gdb_test_no_output "set confirm off"
gdb_test "return" ""
+# Verify there are no remains of the dummy frame.
+gdb_test_no_output "maintenance print dummy-frames"
+set test "maintenance info breakpoints"
+gdb_test_multiple $test $test {
+ -re " call dummy .*\r\n$gdb_prompt $" {
+ fail $test
+ }
+ -re "\r\n$gdb_prompt $" {
+ pass $test
+ }
+}
+
# Resume execution, the program should continue without any signal.
gdb_test "break stop_two" "Breakpoint \[0-9\]* at .*"
diff --git a/gdb/testsuite/gdb.base/stale-infcall.c b/gdb/testsuite/gdb.base/stale-infcall.c
new file mode 100644
index 0000000..1f5169a
--- /dev/null
+++ b/gdb/testsuite/gdb.base/stale-infcall.c
@@ -0,0 +1,63 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2012 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/>. */
+
+#include <setjmp.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define BUFSIZE 0x1000
+
+static jmp_buf jmp;
+
+void
+infcall (void)
+{
+ longjmp (jmp, 1);
+}
+
+static void
+run1 (void)
+{
+ char buf[BUFSIZE / 2];
+ int dummy = 0;
+
+ dummy++; /* break-run1 */
+}
+
+static char buf_zero[BUFSIZE];
+
+static void
+run2 (void)
+{
+ char buf[BUFSIZE];
+
+ memset (buf, 0, sizeof (buf));
+
+ if (memcmp (buf, buf_zero, sizeof (buf)) != 0) /* break-run2 */
+ abort (); /* break-fail */
+}
+
+int
+main ()
+{
+ if (setjmp (jmp) == 0)
+ run1 ();
+ else
+ run2 ();
+
+ return 0; /* break-exit */
+}
diff --git a/gdb/testsuite/gdb.base/stale-infcall.exp b/gdb/testsuite/gdb.base/stale-infcall.exp
new file mode 100644
index 0000000..bb22339
--- /dev/null
+++ b/gdb/testsuite/gdb.base/stale-infcall.exp
@@ -0,0 +1,57 @@
+# Copyright (C) 2012 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/>.
+
+set testfile stale-infcall
+set srcfile ${testfile}.c
+if { [prepare_for_testing $testfile.exp $testfile $srcfile] } {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+gdb_breakpoint [gdb_get_line_number "break-run1"]
+gdb_breakpoint [gdb_get_line_number "break-run2"]
+gdb_breakpoint [gdb_get_line_number "break-exit"]
+gdb_breakpoint [gdb_get_line_number "break-fail"]
+
+gdb_continue_to_breakpoint "break-run1" ".* break-run1 .*"
+
+gdb_test "print infcall ()" " break-run2 .*The program being debugged stopped while in a function called from GDB\\..*When the function is done executing, GDB will silently stop\\."
+
+set test "stack corrupted"
+gdb_test_multiple "continue" $test {
+ -re " break-exit .*\r\n$gdb_prompt $" {
+ pass $test
+ }
+ -re " break-fail .*\r\n$gdb_prompt $" {
+ fail $test
+ }
+}
+
+gdb_test "bt" "#0 \[^\r\n\]* main \[^\r\n\]*"
+
+# Verify there are no remains of the dummy frame.
+gdb_test_no_output "maintenance print dummy-frames"
+set test "maintenance info breakpoints"
+gdb_test_multiple $test $test {
+ -re " call dummy .*\r\n$gdb_prompt $" {
+ fail $test
+ }
+ -re "\r\n$gdb_prompt $" {
+ pass $test
+ }
+}