RFC: next/finish/etc -vs- exceptions

Tom Tromey
2009-05-29T22:32:48Z

With the current gdb, a "next" over a call that throws an exception
can cause gdb to act as though the user typed "continue". This can be
very frustrating when debugging C++ (or Java) code. This also happens
with other commands, particularly "finish", "until", and "advance".

This patch fixes this problem. It relies on a new debugging hook in
GCC's unwinder, so it will only work for GCC 4.5 and later.

It works by putting a breakpoint on the unwinder hook. If an
exception would be thrown above the "next"ing frame, gdb will put an
exception-resume breakpoint at the exception's target PC. GDB ignores
other exceptions (those which would not cause a loss of control).

This seems to work pretty well in my testing. However, this is my
first foray into this part of GDB and I would appreciate comments on
this approach.

Tom

2009-05-29 Tom Tromey <tromey@redhat.com>

* infrun.c (handle_inferior_event): Handle exception breakpoints.
(insert_exception_resume_breakpoint): New function.
(check_exception_resume): Likewise.
* inferior.h: (delete_longjmp_breakpoint_cleanup): Declare.
* infcmd.c (delete_longjmp_breakpoint_cleanup): No longer static.
(step_1): Call set_exception_breakpoint.
(until_next_continuation): New function.
(until_next_command): Call set_exception_breakpoint. Make a
continuation.
(finish_command_continuation): Call delete_longjmp_breakpoint.
(finish_forward): Call set_exception_breakpoint.
* gdbthread.h (struct thread_info) <exception_frame>: New field.
* breakpoint.h (enum bptype) <bp_exception, bp_exception_resume>:
New constants.
(struct bpstat_what) <is_longjmp>: New field.
* breakpoint.c (update_breakpoints_after_exec): Handle
bp_exception and bp_exception_resume.
(print_it_typical): Likewise.
(print_one_breakpoint_location): Likewise.
(allocate_bp_location): Likewise.
(mention): Likewise.
(breakpoint_re_set_one): Likewise.
(bpstat_what): Likewise. Set 'is_longjmp' field.
(delete_longjmp_breakpoint): Handle bp_exception.
(set_exception_breakpoint): New function.
(struct until_break_command_continuation_args) <thread_num>: New
field.
(until_break_command_continuation): Call
delete_longjmp_breakpoint.
(until_break_command): Call set_exception_breakpoint.

2009-05-29 Tom Tromey <tromey@redhat.com>

* gdb.cp/gdb9593.cc: New file.
* gdb.cp/gdb9593.exp: New file.
* gdb.cp/Makefile.in (EXECUTABLES): Add gdb9593.

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 912d7ea..2760f3e 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -2,7 +2,7 @@

Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995,
1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
- 2008 Free Software Foundation, Inc.
+ 2008, 2009 Free Software Foundation, Inc.

This file is part of GDB.

@@ -1480,7 +1480,8 @@ 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)
+ if (b->type == bp_longjmp || b->type == bp_longjmp_resume
+ || b->type == bp_exception || b->type == bp_exception_resume)
{
delete_breakpoint (b);
continue;
@@ -2427,6 +2428,8 @@ print_it_typical (bpstat bs)
case bp_none:
case bp_longjmp:
case bp_longjmp_resume:
+ case bp_exception:
+ case bp_exception_resume:
case bp_step_resume:
case bp_watchpoint_scope:
case bp_call_dummy:
@@ -3266,6 +3269,7 @@ bpstat_what (bpstat bs)
struct bpstat_what retval;

retval.call_dummy = 0;
+ retval.is_longjmp = 0;
for (; bs != NULL; bs = bs->next)
{
enum class bs_class = no_effect;
@@ -3312,10 +3316,15 @@ bpstat_what (bpstat bs)
bs_class = no_effect;
break;
case bp_longjmp:
+ case bp_exception:
bs_class = long_jump;
+ retval.is_longjmp = bs->breakpoint_at->owner->type == bp_longjmp;
break;
case bp_longjmp_resume:
+ case bp_exception_resume:
bs_class = long_resume;
+ retval.is_longjmp
+ = bs->breakpoint_at->owner->type == bp_longjmp_resume;
break;
case bp_step_resume:
if (bs->stop)
@@ -3518,6 +3527,8 @@ print_one_breakpoint_location (struct breakpoint *b,
{bp_access_watchpoint, "acc watchpoint"},
{bp_longjmp, "longjmp"},
{bp_longjmp_resume, "longjmp resume"},
+ {bp_exception, "exception"},
+ {bp_exception_resume, "exception resume"},
{bp_step_resume, "step resume"},
{bp_watchpoint_scope, "watchpoint scope"},
{bp_call_dummy, "call dummy"},
@@ -3669,6 +3680,8 @@ print_one_breakpoint_location (struct breakpoint *b,
case bp_finish:
case bp_longjmp:
case bp_longjmp_resume:
+ case bp_exception:
+ case bp_exception_resume:
case bp_step_resume:
case bp_watchpoint_scope:
case bp_call_dummy:
@@ -4244,6 +4257,8 @@ allocate_bp_location (struct breakpoint *bpt, enum bptype bp_type)
case bp_finish:
case bp_longjmp:
case bp_longjmp_resume:
+ case bp_exception:
+ case bp_exception_resume:
case bp_step_resume:
case bp_watchpoint_scope:
case bp_call_dummy:
@@ -4462,13 +4477,43 @@ delete_longjmp_breakpoint (int thread)
struct breakpoint *b, *temp;

ALL_BREAKPOINTS_SAFE (b, temp)
- if (b->type == bp_longjmp)
+ if (b->type == bp_longjmp || b->type == bp_exception)
{
if (b->thread == thread)
delete_breakpoint (b);
}
}

+/* Install a breakpoint on the unwinder's debug hook, and arrange for
+ the unwinder to call the hook when unwinding to (or past) the
+ current frame. */
+
+CORE_ADDR
+set_exception_breakpoint (struct frame_info *frame)
+{
+ struct minimal_symbol *debug_hook;
+ CORE_ADDR stack_ptr = 0;
+
+ debug_hook = lookup_minimal_symbol_text ("_Unwind_DebugHook", NULL);
+ if (debug_hook != NULL)
+ {
+ struct value *lhs, *rhs;
+ CORE_ADDR pc;
+
+ /* We use the current stack pointer and not the CFA here,
+ because the unwinder seems to compute the callee's CFA, and
+ so the breakpoint does not trigger. */
+ stack_ptr = get_frame_sp (frame);
+
+ pc = find_function_start_pc (get_frame_arch (frame),
+ SYMBOL_VALUE_ADDRESS (debug_hook),
+ SYMBOL_OBJ_SECTION (debug_hook));
+ set_momentary_breakpoint_at_pc (pc, bp_exception);
+ }
+
+ return stack_ptr;
+}
+
static void
create_overlay_event_breakpoint_1 (char *func_name, struct objfile *objfile)
{
@@ -5158,6 +5203,8 @@ mention (struct breakpoint *b)
case bp_finish:
case bp_longjmp:
case bp_longjmp_resume:
+ case bp_exception:
+ case bp_exception_resume:
case bp_step_resume:
case bp_call_dummy:
case bp_watchpoint_scope:
@@ -6356,6 +6403,7 @@ struct until_break_command_continuation_args
{
struct breakpoint *breakpoint;
struct breakpoint *breakpoint2;
+ int thread_num;
};

/* This function is called by fetch_inferior_event via the
@@ -6370,6 +6418,7 @@ until_break_command_continuation (void *arg)
delete_breakpoint (a->breakpoint);
if (a->breakpoint2)
delete_breakpoint (a->breakpoint2);
+ delete_longjmp_breakpoint (a->thread_num);
}

void
@@ -6382,6 +6431,8 @@ until_break_command (char *arg, int from_tty, int anywhere)
struct breakpoint *breakpoint;
struct breakpoint *breakpoint2 = NULL;
struct cleanup *old_chain;
+ int thread;
+ struct thread_info *tp;

clear_proceed_status ();

@@ -6418,6 +6469,9 @@ until_break_command (char *arg, int from_tty, int anywhere)

old_chain = make_cleanup_delete_breakpoint (breakpoint);

+ tp = inferior_thread ();
+ thread = tp->num;
+
/* Keep within the current frame, or in frames called by the current
one. */
if (prev_frame)
@@ -6427,6 +6481,10 @@ until_break_command (char *arg, int from_tty, int anywhere)
breakpoint2 = set_momentary_breakpoint (sal, get_frame_id (prev_frame),
bp_until);
make_cleanup_delete_breakpoint (breakpoint2);
+
+ /* Install exception-handling breakpoint. */
+ tp->exception_frame = set_exception_breakpoint (prev_frame);
+ make_cleanup (delete_longjmp_breakpoint_cleanup, &thread);
}

proceed (-1, TARGET_SIGNAL_DEFAULT, 0);
@@ -6443,6 +6501,7 @@ until_break_command (char *arg, int from_tty, int anywhere)

args->breakpoint = breakpoint;
args->breakpoint2 = breakpoint2;
+ args->thread_num = thread;

discard_cleanups (old_chain);
add_continuation (inferior_thread (),
@@ -7777,6 +7836,8 @@ breakpoint_re_set_one (void *bint)
case bp_step_resume:
case bp_longjmp:
case bp_longjmp_resume:
+ case bp_exception:
+ case bp_exception_resume:
break;
}

diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index a77b1b4..a5b9728 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -1,6 +1,6 @@
/* Data structures associated with breakpoints in GDB.
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
- 2002, 2003, 2004, 2007, 2008 Free Software Foundation, Inc.
+ 2002, 2003, 2004, 2007, 2008, 2009 Free Software Foundation, Inc.

This file is part of GDB.

@@ -55,6 +55,9 @@ enum bptype
bp_longjmp, /* secret breakpoint to find longjmp() */
bp_longjmp_resume, /* secret breakpoint to escape longjmp() */

+ bp_exception,
+ bp_exception_resume,
+
/* Used by wait_for_inferior for stepping over subroutine calls, for
stepping over signal handlers, and for skipping prologues. */
bp_step_resume,
@@ -554,6 +557,10 @@ struct bpstat_what
continuing from a call dummy without popping the frame is not a
useful one). */
int call_dummy;
+
+ /* Used for BPSTAT_WHAT_SET_LONGJMP_RESUME. True if we are
+ handling a longjmp, false if we are handling an exception. */
+ int is_longjmp;
};

/* The possible return values for print_bpstat, print_it_normal,
@@ -777,6 +784,8 @@ extern int detach_breakpoints (int);
extern void set_longjmp_breakpoint (void);
extern void delete_longjmp_breakpoint (int thread);

+extern CORE_ADDR set_exception_breakpoint (struct frame_info *);
+
extern void enable_overlay_breakpoints (void);
extern void disable_overlay_breakpoints (void);

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 55c848d..b4bb97c 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -171,6 +171,9 @@ struct thread_info
/* True if this thread has been explicitly requested to stop. */
int stop_requested;

+ /* The target CFA of an exception. */
+ CORE_ADDR exception_frame;
+
/* Private data used by the target vector implementation. */
struct private_thread_info *private;
};
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 50e8fff..a3ed951 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -2,7 +2,7 @@

Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995,
1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
- 2008 Free Software Foundation, Inc.
+ 2008, 2009 Free Software Foundation, Inc.

This file is part of GDB.

@@ -754,7 +754,7 @@ nexti_command (char *count_string, int from_tty)
step_1 (1, 1, count_string);
}

-static void
+void
delete_longjmp_breakpoint_cleanup (void *arg)
{
int thread = * (int *) arg;
@@ -793,11 +793,13 @@ step_1 (int skip_subroutines, int single_inst, char *count_string)

if (!single_inst || skip_subroutines) /* leave si command alone */
{
+ struct thread_info *tp = inferior_thread ();
+
if (in_thread_list (inferior_ptid))
thread = pid_to_thread_id (inferior_ptid);

set_longjmp_breakpoint ();
-
+ tp->exception_frame = set_exception_breakpoint (get_current_frame ());
make_cleanup (delete_longjmp_breakpoint_cleanup, &thread);
}

@@ -1154,6 +1156,15 @@ signal_command (char *signum_exp, int from_tty)
proceed (oursig == TARGET_SIGNAL_0 ? (CORE_ADDR) -1 : stop_pc, oursig, 0);
}

+/* A continuation callback for until_next_command. */
+
+static void
+until_next_continuation (void *arg)
+{
+ struct thread_info *tp = arg;
+ delete_longjmp_breakpoint (tp->num);
+}
+
/* Proceed until we reach a different source line with pc greater than
our current one or exit the function. We skip calls in both cases.

@@ -1170,6 +1181,8 @@ until_next_command (int from_tty)
struct symbol *func;
struct symtab_and_line sal;
struct thread_info *tp = inferior_thread ();
+ int thread = tp->num;
+ struct cleanup *old_chain;

clear_proceed_status ();

@@ -1205,7 +1218,18 @@ until_next_command (int from_tty)

tp->step_multi = 0; /* Only one call to proceed */

+ tp->exception_frame = set_exception_breakpoint (frame);
+ old_chain = make_cleanup (delete_longjmp_breakpoint_cleanup, &thread);
+
proceed ((CORE_ADDR) -1, TARGET_SIGNAL_DEFAULT, 1);
+
+ if (target_can_async_p () && is_running (inferior_ptid))
+ {
+ discard_cleanups (old_chain);
+ add_continuation (tp, until_next_continuation, tp, NULL);
+ }
+ else
+ do_cleanups (old_chain);
}

static void
@@ -1383,6 +1407,7 @@ finish_command_continuation (void *arg)
observer_notify_normal_stop (bs);
suppress_stop_observer = 0;
delete_breakpoint (a->breakpoint);
+ delete_longjmp_breakpoint (inferior_thread ()->num);
}

static void
@@ -1463,6 +1488,7 @@ finish_forward (struct symbol *function, struct frame_info *frame)
struct breakpoint *breakpoint;
struct cleanup *old_chain;
struct finish_command_continuation_args *cargs;
+ int thread = tp->num;

sal = find_pc_line (get_frame_pc (frame), 0);
sal.pc = get_frame_pc (frame);
@@ -1472,6 +1498,9 @@ finish_forward (struct symbol *function, struct frame_info *frame)

old_chain = make_cleanup_delete_breakpoint (breakpoint);

+ tp->exception_frame = set_exception_breakpoint (frame);
+ make_cleanup (delete_longjmp_breakpoint_cleanup, &thread);
+
tp->proceed_to_finish = 1; /* We want stop_registers, please... */
make_cleanup_restore_integer (&suppress_stop_observer);
suppress_stop_observer = 1;
diff --git a/gdb/inferior.h b/gdb/inferior.h
index cc5bf9f..3e62a2f 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -2,7 +2,7 @@
Where it is, why it stopped, and how to step it.

Copyright (C) 1986, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996,
- 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, 2008
+ 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, 2008, 2009
Free Software Foundation, Inc.

This file is part of GDB.
@@ -269,6 +269,8 @@ extern void interrupt_target_command (char *args, int from_tty);

extern void interrupt_target_1 (int all_threads);

+extern void delete_longjmp_breakpoint_cleanup (void *arg);
+
/* Address at which inferior stopped. */

extern CORE_ADDR stop_pc;
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 30d914d..8997cf5 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -3,7 +3,7 @@

Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995,
1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
- 2008 Free Software Foundation, Inc.
+ 2008, 2009 Free Software Foundation, Inc.

This file is part of GDB.

@@ -45,6 +45,8 @@
#include "language.h"
#include "solib.h"
#include "main.h"
+#include "dictionary.h"
+#include "block.h"

#include "gdb_assert.h"
#include "mi/mi-common.h"
@@ -1551,6 +1553,8 @@ static void insert_step_resume_breakpoint_at_caller (struct frame_info *);
static void insert_step_resume_breakpoint_at_sal (struct symtab_and_line sr_sal,
struct frame_id sr_id);
static void insert_longjmp_resume_breakpoint (CORE_ADDR);
+static void check_exception_resume (struct execution_control_state *,
+ struct frame_info *, struct symbol *);

static void stop_stepping (struct execution_control_state *ecs);
static void prepare_to_wait (struct execution_control_state *ecs);
@@ -3019,24 +3023,34 @@ process_event_stop_test:

ecs->event_thread->stepping_over_breakpoint = 1;

- if (!gdbarch_get_longjmp_target_p (current_gdbarch)
- || !gdbarch_get_longjmp_target (current_gdbarch,
- get_current_frame (), &jmp_buf_pc))
+ if (what.is_longjmp)
{
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog, "\
+ if (!gdbarch_get_longjmp_target_p (current_gdbarch)
+ || !gdbarch_get_longjmp_target (current_gdbarch,
+ get_current_frame (), &jmp_buf_pc))
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "\
infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (!gdbarch_get_longjmp_target)\n");
- keep_going (ecs);
- return;
- }
+ keep_going (ecs);
+ return;
+ }

- /* We're going to replace the current step-resume breakpoint
- with a longjmp-resume breakpoint. */
- delete_step_resume_breakpoint (ecs->event_thread);
+ /* We're going to replace the current step-resume breakpoint
+ with a longjmp-resume breakpoint. */
+ delete_step_resume_breakpoint (ecs->event_thread);

- /* Insert a breakpoint at resume address. */
- insert_longjmp_resume_breakpoint (jmp_buf_pc);
+ /* Insert a breakpoint at resume address. */
+ insert_longjmp_resume_breakpoint (jmp_buf_pc);
+ }
+ else
+ {
+ struct frame_info *frame = get_current_frame ();
+ struct symbol *func = get_frame_function (frame);

+ if (func)
+ check_exception_resume (ecs, frame, func);
+ }
keep_going (ecs);
return;

@@ -3048,6 +3062,17 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (!gdbarch_get_longjmp_target)\n");
gdb_assert (ecs->event_thread->step_resume_breakpoint != NULL);
delete_step_resume_breakpoint (ecs->event_thread);

+ if (!what.is_longjmp && ecs->event_thread->step_range_start
+ && (get_frame_sp (get_current_frame ())
+ == ecs->event_thread->exception_frame))
+ {
+ /* The inferior threw an exception which landed in the
+ frame in which we started stepping. In this case we
+ defer to the stepping logic to decide whether to stop.
+ In all other exception cases, we always stop. */
+ break;
+ }
+
ecs->event_thread->stop_step = 1;
print_stop_reason (END_STEPPING_RANGE, 0);
stop_stepping (ecs);
@@ -3851,6 +3876,112 @@ insert_longjmp_resume_breakpoint (CORE_ADDR pc)
set_momentary_breakpoint_at_pc (pc, bp_longjmp_resume);
}

+/* Insert an exception resume breakpoint. TP is the thread throwing
+ the exception. The block B is the block of the unwinder debug hook
+ function. FRAME is the frame corresponding to the call to this
+ function. SYM is the symbol of the function argument holding the
+ target PC of the exception. */
+
+static void
+insert_exception_resume_breakpoint (struct thread_info *tp,
+ struct block *b,
+ struct frame_info *frame,
+ struct symbol *sym)
+{
+ struct gdb_exception e;
+
+ /* We want to ignore errors here. */
+ TRY_CATCH (e, RETURN_MASK_ALL)
+ {
+ struct symbol *vsym;
+ struct value *value;
+ CORE_ADDR handler;
+ struct breakpoint *bp;
+
+ vsym = lookup_symbol (SYMBOL_LINKAGE_NAME (sym), b, VAR_DOMAIN, NULL);
+ value = read_var_value (vsym, frame);
+ handler = value_as_address (value);
+
+ /* We're going to replace the current step-resume breakpoint
+ with a longjmp-resume breakpoint. */
+ delete_step_resume_breakpoint (tp);
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: exception resume at %lx\n",
+ (unsigned long) handler);
+
+ bp = set_momentary_breakpoint_at_pc (handler, bp_exception_resume);
+ inferior_thread ()->step_resume_breakpoint = bp;
+ }
+}
+
+/* This is called when an exception has been intercepted. Check to
+ see whether the exception's destination is of interest, and if so,
+ set an exception resume breakpoint there. */
+
+static void
+check_exception_resume (struct execution_control_state *ecs,
+ struct frame_info *frame, struct symbol *func)
+{
+ struct gdb_exception e;
+
+ TRY_CATCH (e, RETURN_MASK_ALL)
+ {
+ struct block *b;
+ struct dict_iterator iter;
+ struct symbol *sym;
+ int argno = 0;
+
+ b = SYMBOL_BLOCK_VALUE (func);
+ ALL_BLOCK_SYMBOLS (b, iter, sym)
+ {
+ if (!SYMBOL_IS_ARGUMENT (sym))
+ continue;
+
+ if (argno == 0)
+ {
+ struct symbol *vsym;
+ struct value *value;
+ CORE_ADDR cfa, nexting_cfa;
+ struct gdbarch *arch;
+
+ vsym = lookup_symbol (SYMBOL_LINKAGE_NAME (sym),
+ b, VAR_DOMAIN, NULL);
+ value = read_var_value (vsym, frame);
+ cfa = value_as_address (value);
+
+ arch = get_frame_arch (frame);
+ nexting_cfa = ecs->event_thread->exception_frame;
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: comparing exception target %lx with next-frame %lx: %d\n",
+ (unsigned long) cfa,
+ (unsigned long) nexting_cfa,
+ !nexting_cfa || gdbarch_inner_than (arch, cfa, nexting_cfa));
+
+ if (!nexting_cfa
+ || gdbarch_inner_than (arch, cfa, nexting_cfa))
+ {
+ /* Not an interesting exception. */
+ break;
+ }
+ ++argno;
+ }
+ else
+ {
+ /* If we got here, then we want to set the exception
+ resume breakpoint at the address held in the second
+ argument to the function. */
+
+ insert_exception_resume_breakpoint (ecs->event_thread,
+ b, frame, sym);
+ break;
+ }
+ }
+ }
+}
+
static void
stop_stepping (struct execution_control_state *ecs)
{
@@ -3917,6 +4048,8 @@ keep_going (struct execution_control_state *ecs)
}
if (e.reason < 0)
{
+ if (debug_infrun)
+ exception_fprintf (gdb_stdlog, e, "infrun: exception while inserting breakpoints: ");
stop_stepping (ecs);
return;
}
diff --git a/gdb/testsuite/gdb.cp/Makefile.in b/gdb/testsuite/gdb.cp/Makefile.in
index f4a989c..1a2b908 100644
--- a/gdb/testsuite/gdb.cp/Makefile.in
+++ b/gdb/testsuite/gdb.cp/Makefile.in
@@ -4,7 +4,7 @@ srcdir = @srcdir@
EXECUTABLES = ambiguous annota2 anon-union cplusfuncs cttiadd \
derivation inherit local member-ptr method misc \
overload ovldbreak ref-typ ref-typ2 templates userdef virtfunc namespace \
- ref-types ref-params method2
+ ref-types ref-params method2 gdb9593

all info install-info dvi install uninstall installcheck check:
@echo "Nothing to be done for $@..."
diff --git a/gdb/testsuite/gdb.cp/gdb9593.cc b/gdb/testsuite/gdb.cp/gdb9593.cc
new file mode 100644
index 0000000..3092f57
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/gdb9593.cc
@@ -0,0 +1,180 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2008, 2009 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 <iostream>
+
+using namespace std;
+
+class NextOverThrowDerivates
+{
+
+public:
+
+
+ // Single throw an exception in this function.
+ void function1()
+ {
+ throw 20;
+ }
+
+ // Throw an exception in another function.
+ void function2()
+ {
+ function1();
+ }
+
+ // Throw an exception in another function, but handle it
+ // locally.
+ void function3 ()
+ {
+ {
+ try
+ {
+ function1 ();
+ }
+ catch (...)
+ {
+ cout << "Caught and handled function1 exception" << endl;
+ }
+ }
+ }
+
+ void rethrow ()
+ {
+ try
+ {
+ function1 ();
+ }
+ catch (...)
+ {
+ throw;
+ }
+ }
+
+ void finish ()
+ {
+ // We use this to test that a "finish" here does not end up in
+ // this frame, but in the one above.
+ try
+ {
+ function1 ();
+ }
+ catch (int x)
+ {
+ }
+ function1 ();
+ }
+
+ void until ()
+ {
+ function1 ();
+ function1 (); // until here
+ }
+
+};
+NextOverThrowDerivates next_cases;
+
+
+int main ()
+{
+ try
+ {
+ next_cases.function1 ();
+ }
+ catch (...)
+ {
+ // Discard
+ }
+
+ try
+ {
+ next_cases.function2 ();
+ }
+ catch (...)
+ {
+ // Discard
+ }
+
+ try
+ {
+ // This is duplicated so we can next over one but step into
+ // another.
+ next_cases.function2 ();
+ }
+ catch (...)
+ {
+ // Discard
+ }
+
+ next_cases.function3 ();
+
+ try
+ {
+ next_cases.rethrow ();
+ }
+ catch (...)
+ {
+ // Discard
+ }
+
+ try
+ {
+ // Another duplicate so we can test "finish".
+ next_cases.function2 ();
+ }
+ catch (...)
+ {
+ // Discard
+ }
+
+ // Another test for "finish".
+ try
+ {
+ next_cases.finish ();
+ }
+ catch (...)
+ {
+ }
+
+ // Test of "until".
+ try
+ {
+ next_cases.finish ();
+ }
+ catch (...)
+ {
+ }
+
+ // Test of "until" with an argument.
+ try
+ {
+ next_cases.until ();
+ }
+ catch (...)
+ {
+ }
+
+ // Test of "advance".
+ try
+ {
+ next_cases.until ();
+ }
+ catch (...)
+ {
+ }
+}
+
diff --git a/gdb/testsuite/gdb.cp/gdb9593.exp b/gdb/testsuite/gdb.cp/gdb9593.exp
new file mode 100644
index 0000000..b0fcfbc
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/gdb9593.exp
@@ -0,0 +1,183 @@
+# Copyright 2008, 2009 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/>.
+
+
+if $tracelevel then {
+ strace $tracelevel
+}
+
+if { [skip_cplus_tests] } { continue }
+
+set prms_id 9593
+set bug_id 0
+
+set testfile "gdb9593"
+set srcfile ${testfile}.cc
+set binfile $objdir/$subdir/$testfile
+
+# Create and source the file that provides information about the compiler
+# used to compile the test case.
+if [get_compiler_info ${binfile} "c++"] {
+ untested gdb9593.exp
+ return -1
+}
+
+if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug c++}] != "" } {
+ untested gdb9593.exp
+ return -1
+}
+
+# Some targets can't do function calls, so don't even bother with this
+# test.
+if [target_info exists gdb,cannot_call_functions] {
+ setup_xfail "*-*-*" 9593
+ fail "This target can not call functions"
+ continue
+}
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+if ![runto_main] then {
+ perror "couldn't run to main"
+ continue
+}
+
+# See whether we have the needed unwinder hooks.
+set ok 1
+gdb_test_multiple "print _Unwind_DebugHook" "check for unwinder hook" {
+ -re "= .*_Unwind_DebugHook.*\r\n$gdb_prompt $" {
+ pass "check for unwinder hook"
+ }
+ -re "No symbol .* in current context.\r\n$gdb_prompt $" {
+ # Pass the test so we don't get bogus fails in the results.
+ pass "check for unwinder hook"
+ set ok 0
+ }
+}
+if {!$ok} {
+ untested gdb9593.exp
+ return -1
+}
+
+# See http://sourceware.org/bugzilla/show_bug.cgi?id=9593
+
+gdb_test "next" \
+ ".*catch (...).*" \
+ "next over a throw 1"
+
+gdb_test "next" \
+ ".*next_cases.function2.*" \
+ "next past catch 1"
+
+gdb_test "next" \
+ ".*catch (...).*" \
+ "next over a throw 2"
+
+gdb_test "next" \
+ ".*next_cases.function2.*" \
+ "next past catch 2"
+
+gdb_test "step" \
+ ".*function1().*" \
+ "step into function2 1"
+
+gdb_test "next" \
+ ".*catch (...).*" \
+ "next over a throw 3"
+
+gdb_test "next" \
+ ".*next_cases.function3.*" \
+ "next past catch 3"
+
+gdb_test "next" \
+ ".*next_cases.rethrow.*" \
+ "next over a throw 4"
+
+gdb_test "next" \
+ ".*catch (...).*" \
+ "next over a rethrow"
+
+gdb_test "next" \
+ ".*next_cases.function2.*" \
+ "next after a rethrow"
+
+gdb_test "step" \
+ ".*function1().*" \
+ "step into function2 2"
+
+gdb_test "finish" \
+ ".*catch (...).*" \
+ "finish 1"
+
+gdb_test "next" \
+ ".*next_cases.finish ().*" \
+ "next past catch 4"
+
+gdb_test "step" \
+ ".*function1 ().*" \
+ "step into finish method"
+
+gdb_test "finish" \
+ ".*catch (...).*" \
+ "finish 2"
+
+gdb_test "next" \
+ ".*next_cases.finish ().*" \
+ "next past catch 5"
+
+gdb_test "step" \
+ ".*function1 ().*" \
+ "step into finish, for until"
+
+gdb_test "until" \
+ ".*catch .int x.*" \
+ "until with no argument 1"
+
+gdb_test "next" \
+ ".*function1 ().*" \
+ "next past catch 6"
+
+gdb_test "until" \
+ ".*catch (...).*" \
+ "until with no argument 2"
+
+set line [gdb_get_line_number "until here" $testfile.cc]
+
+gdb_test "next" \
+ ".*next_cases.until ().*" \
+ "next past catch 6"
+
+gdb_test "step" \
+ ".*function1 ().*" \
+ "step into until"
+
+gdb_test "until $line" \
+ ".*catch (...).*" \
+ "until-over-throw"
+
+gdb_test "next" \
+ ".*next_cases.until ().*" \
+ "next past catch 7"
+
+gdb_test "step" \
+ ".*function1 ().*" \
+ "step into until, for advance"
+
+gdb_test "advance $line" \
+ ".*catch (...).*" \
+ "advance-over-throw"