+2016-04-22 Pedro Alves <palves@redhat.com>
+
+ * common/common-exceptions.c (enum catcher_state, struct catcher)
+ (current_catcher): Define in C++ mode too.
+ (exceptions_state_mc_catch): Call throw_exception_sjlj instead of
+ throw_exception.
+ (throw_exception_sjlj, throw_exception_cxx): New functions,
+ factored out from throw_exception.
+ (throw_exception): Reimplement.
+ * common/common-exceptions.h (exceptions_state_mc_init)
+ (exceptions_state_mc_action_iter)
+ (exceptions_state_mc_action_iter_1, exceptions_state_mc_catch):
+ Declare in C++ mode too.
+ (TRY): Rename to ...
+ (TRY_SJLJ): ... this.
+ (CATCH): Rename to ...
+ (CATCH_SJLJ): ... this.
+ (END_CATCH): Rename to ...
+ (END_CATCH_SJLJ): ... this.
+ [GDB_XCPT == GDB_XCPT_SJMP] (TRY, CATCH, END_CATCH): Map to SJLJ
+ equivalents.
+ (throw_exception): Update comments.
+ (throw_exception_sjlj): Declare.
+ * event-top.c (gdb_rl_callback_read_char_wrapper): Extend intro
+ comment. Wrap body in TRY_SJLJ/CATCH_SJLJ and rethrow any
+ intercepted exception.
+ (gdb_rl_callback_handler): New function.
+ (gdb_rl_callback_handler_install): Always install
+ gdb_rl_callback_handler as readline callback.
+
2016-04-22 Pedro Alves <palves@redhat.com>
* event-top.c (rl_callback_read_char_wrapper): Rename to ...
const struct gdb_exception exception_none = { (enum return_reason) 0, GDB_NO_ERROR, NULL };
-#if GDB_XCPT == GDB_XCPT_SJMP
-
/* Possible catcher states. */
enum catcher_state {
/* Initial state, a new catcher has just been created. */
/* Where to go for throw_exception(). */
static struct catcher *current_catcher;
+#if GDB_XCPT == GDB_XCPT_SJMP
+
/* Return length of current_catcher list. */
static int
return size;
}
+#endif
+
jmp_buf *
exceptions_state_mc_init (void)
{
}
/* The caller didn't request that the event be caught, relay the
- event to the next exception_catch/CATCH. */
- throw_exception (*exception);
+ event to the next exception_catch/CATCH_SJLJ. */
+ throw_exception_sjlj (*exception);
}
/* No exception was thrown. */
return exceptions_state_mc (CATCH_ITER_1);
}
-#else /* !GDB_XCPT_SJMP */
+#if GDB_XCPT != GDB_XCPT_SJMP
/* How many nested TRY blocks we have. See exception_messages and
throw_it. */
/* Return EXCEPTION to the nearest containing catch_errors(). */
void
-throw_exception (struct gdb_exception exception)
+throw_exception_sjlj (struct gdb_exception exception)
{
do_cleanups (all_cleanups ());
-#if GDB_XCPT == GDB_XCPT_SJMP
/* Jump to the containing catch_errors() call, communicating REASON
to that call via setjmp's return value. Note that REASON can't
be zero, by definition in defs.h. */
exceptions_state_mc (CATCH_THROWING);
current_catcher->exception = exception;
longjmp (current_catcher->buf, exception.reason);
-#else
+}
+
+#if GDB_XCPT != GDB_XCPT_SJMP
+
+/* Implementation of throw_exception that uses C++ try/catch. */
+
+static ATTRIBUTE_NORETURN void
+throw_exception_cxx (struct gdb_exception exception)
+{
+ do_cleanups (all_cleanups ());
+
if (exception.reason == RETURN_QUIT)
{
gdb_exception_RETURN_MASK_QUIT ex;
}
else
gdb_assert_not_reached ("invalid return reason");
+}
+
+#endif
+
+void
+throw_exception (struct gdb_exception exception)
+{
+#if GDB_XCPT == GDB_XCPT_SJMP
+ throw_exception_sjlj (exception);
+#else
+ throw_exception_cxx (exception);
#endif
}
/* Always use setjmp/longmp, even in C++ mode. */
#define GDB_XCPT GDB_XCPT_SJMP
-/* Functions to drive the exceptions state machine. Though declared
- here by necessity, these functions should be considered internal to
- the exceptions subsystem and not used other than via the TRY/CATCH
- macros defined below. */
+/* Functions to drive the sjlj-based exceptions state machine. Though
+ declared here by necessity, these functions should be considered
+ internal to the exceptions subsystem and not used other than via
+ the TRY/CATCH (or TRY_SJLJ/CATCH_SJLJ) macros defined below. */
-#if GDB_XCPT == GDB_XCPT_SJMP
extern jmp_buf *exceptions_state_mc_init (void);
extern int exceptions_state_mc_action_iter (void);
extern int exceptions_state_mc_action_iter_1 (void);
extern int exceptions_state_mc_catch (struct gdb_exception *, int);
-#else
+
+/* Same, but for the C++ try/catch-based TRY/CATCH mechanism. */
+
+#if GDB_XCPT != GDB_XCPT_SJMP
extern void *exception_try_scope_entry (void);
extern void exception_try_scope_exit (void *saved_state);
extern void exception_rethrow (void);
}
END_CATCH
- */
+ Note that the SJLJ version of the macros are actually named
+ TRY_SJLJ/CATCH_SJLJ in order to make it possible to call them even
+ when TRY/CATCH are mapped to C++ try/catch. The SJLJ variants are
+ needed in some cases where gdb exceptions need to cross third-party
+ library code compiled without exceptions support (e.g.,
+ readline). */
-#if GDB_XCPT == GDB_XCPT_SJMP
-
-#define TRY \
+#define TRY_SJLJ \
{ \
jmp_buf *buf = \
exceptions_state_mc_init (); \
while (exceptions_state_mc_action_iter ()) \
while (exceptions_state_mc_action_iter_1 ())
-#define CATCH(EXCEPTION, MASK) \
+#define CATCH_SJLJ(EXCEPTION, MASK) \
{ \
struct gdb_exception EXCEPTION; \
if (exceptions_state_mc_catch (&(EXCEPTION), MASK))
-#define END_CATCH \
+#define END_CATCH_SJLJ \
}
+#if GDB_XCPT == GDB_XCPT_SJMP
+
+/* If using SJLJ-based exceptions for all exceptions, then provide
+ standard aliases. */
+
+#define TRY TRY_SJLJ
+#define CATCH CATCH_SJLJ
+#define END_CATCH END_CATCH_SJLJ
+
#endif /* GDB_XCPT_SJMP */
#if GDB_XCPT == GDB_XCPT_TRY || GDB_XCPT == GDB_XCPT_RAW_TRY
/* *INDENT-ON* */
-/* Throw an exception (as described by "struct gdb_exception"). Will
- execute a LONG JUMP to the inner most containing exception handler
- established using catch_exceptions() (or similar).
-
- Code normally throws an exception using error() et.al. For various
- reaons, GDB also contains code that throws an exception directly.
- For instance, the remote*.c targets contain CNTRL-C signal handlers
- that propogate the QUIT event up the exception chain. ``This could
- be a good thing or a dangerous thing.'' -- the Existential
- Wombat. */
-
+/* Throw an exception (as described by "struct gdb_exception"). When
+ GDB is built as a C program, executes a LONG JUMP to the inner most
+ containing exception handler established using TRY/CATCH. When
+ built as a C++ program, throws a C++ exception, using "throw". */
extern void throw_exception (struct gdb_exception exception)
ATTRIBUTE_NORETURN;
+
+/* Throw an exception by executing a LONG JUMP to the inner most
+ containing exception handler established using TRY_SJLJ. Works the
+ same regardless of whether GDB is built as a C program or a C++
+ program. Necessary in some cases where we need to throw GDB
+ exceptions across third-party library code (e.g., readline). */
+extern void throw_exception_sjlj (struct gdb_exception exception)
+ ATTRIBUTE_NORETURN;
+
+/* Convenience wrappers around throw_exception that throw GDB
+ errors. */
extern void throw_verror (enum errors, const char *fmt, va_list ap)
ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF (2, 0);
extern void throw_vquit (const char *fmt, va_list ap)
void (*after_char_processing_hook) (void);
\f
-/* Wrapper function for calling into the readline library. The event
- loop expects the callback function to have a paramter, while
- readline expects none. */
+/* Wrapper function for calling into the readline library. This takes
+ care of a couple things:
+
+ - The event loop expects the callback function to have a parameter,
+ while readline expects none.
+
+ - Propagation of GDB exceptions/errors thrown from INPUT_HANDLER
+ across readline requires special handling.
+
+ On the exceptions issue:
+
+ DWARF-based unwinding cannot cross code built without -fexceptions.
+ Any exception that tries to propagate through such code will fail
+ and the result is a call to std::terminate. While some ABIs, such
+ as x86-64, require all code to be built with exception tables,
+ others don't.
+
+ This is a problem when GDB calls some non-EH-aware C library code,
+ that calls into GDB again through a callback, and that GDB callback
+ code throws a C++ exception. Turns out this is exactly what
+ happens with GDB's readline callback.
+
+ In such cases, we must catch and save any C++ exception that might
+ be thrown from the GDB callback before returning to the
+ non-EH-aware code. When the non-EH-aware function itself returns
+ back to GDB, we then rethrow the original C++ exception.
+
+ In the readline case however, the right thing to do is to longjmp
+ out of the callback, rather than do a normal return -- there's no
+ way for the callback to return to readline an indication that an
+ error happened, so a normal return would have rl_callback_read_char
+ potentially continue processing further input, redisplay the
+ prompt, etc. Instead of raw setjmp/longjmp however, we use our
+ sjlj-based TRY/CATCH mechanism, which knows to handle multiple
+ levels of active setjmp/longjmp frames, needed in order to handle
+ the readline callback recursing, as happens with e.g., secondary
+ prompts / queries, through gdb_readline_wrapper. */
+
static void
gdb_rl_callback_read_char_wrapper (gdb_client_data client_data)
{
- rl_callback_read_char ();
- if (after_char_processing_hook)
- (*after_char_processing_hook) ();
+ struct gdb_exception gdb_expt = exception_none;
+
+ /* C++ exceptions can't normally be thrown across readline (unless
+ it is built with -fexceptions, but it won't by default on many
+ ABIs). So we instead wrap the readline call with a sjlj-based
+ TRY/CATCH, and rethrow the GDB exception once back in GDB. */
+ TRY_SJLJ
+ {
+ rl_callback_read_char ();
+ if (after_char_processing_hook)
+ (*after_char_processing_hook) ();
+ }
+ CATCH_SJLJ (ex, RETURN_MASK_ALL)
+ {
+ gdb_expt = ex;
+ }
+ END_CATCH_SJLJ
+
+ /* Rethrow using the normal EH mechanism. */
+ if (gdb_expt.reason < 0)
+ throw_exception (gdb_expt);
+}
+
+/* GDB's readline callback handler. Calls the current INPUT_HANDLER,
+ and propagates GDB exceptions/errors thrown from INPUT_HANDLER back
+ across readline. See gdb_rl_callback_read_char_wrapper. */
+
+static void
+gdb_rl_callback_handler (char *rl)
+{
+ struct gdb_exception gdb_rl_expt = exception_none;
+
+ TRY
+ {
+ input_handler (rl);
+ }
+ CATCH (ex, RETURN_MASK_ALL)
+ {
+ gdb_rl_expt = ex;
+ }
+ END_CATCH
+
+ /* If we caught a GDB exception, longjmp out of the readline
+ callback. There's no other way for the callback to signal to
+ readline that an error happened. A normal return would have
+ readline potentially continue processing further input, redisplay
+ the prompt, etc. (This is what GDB historically did when it was
+ a C program.) Note that since we're long jumping, local variable
+ dtors are NOT run automatically. */
+ if (gdb_rl_expt.reason < 0)
+ throw_exception_sjlj (gdb_rl_expt);
}
/* Initialize all the necessary variables, start the event loop,
therefore loses input. */
gdb_assert (!callback_handler_installed);
- rl_callback_handler_install (prompt, input_handler);
+ rl_callback_handler_install (prompt, gdb_rl_callback_handler);
callback_handler_installed = 1;
}