diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 37c4be04a0..408a179da5 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,33 @@ +2016-04-22 Pedro Alves + + * 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 * event-top.c (rl_callback_read_char_wrapper): Rename to ... diff --git a/gdb/common/common-exceptions.c b/gdb/common/common-exceptions.c index 2e638620b8..33fff21609 100644 --- a/gdb/common/common-exceptions.c +++ b/gdb/common/common-exceptions.c @@ -22,8 +22,6 @@ 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. */ @@ -57,6 +55,8 @@ struct catcher /* 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 @@ -73,6 +73,8 @@ catcher_list_size (void) return size; } +#endif + jmp_buf * exceptions_state_mc_init (void) { @@ -193,8 +195,8 @@ exceptions_state_mc_catch (struct gdb_exception *exception, } /* 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. */ @@ -213,7 +215,7 @@ exceptions_state_mc_action_iter_1 (void) 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. */ @@ -265,18 +267,27 @@ gdb_exception_sliced_copy (struct gdb_exception *to, const struct gdb_exception /* 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; @@ -293,6 +304,17 @@ throw_exception (struct gdb_exception exception) } 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 } diff --git a/gdb/common/common-exceptions.h b/gdb/common/common-exceptions.h index 1ef3db35f7..84abcae81b 100644 --- a/gdb/common/common-exceptions.h +++ b/gdb/common/common-exceptions.h @@ -136,17 +136,19 @@ struct gdb_exception /* 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); @@ -175,11 +177,14 @@ 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 (); \ @@ -188,14 +193,23 @@ extern void exception_rethrow (void); 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 @@ -270,19 +284,23 @@ struct gdb_exception_RETURN_MASK_QUIT : public gdb_exception_RETURN_MASK_ALL /* *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) diff --git a/gdb/event-top.c b/gdb/event-top.c index b9947c48fa..f43fd0a340 100644 --- a/gdb/event-top.c +++ b/gdb/event-top.c @@ -145,15 +145,98 @@ static struct async_signal_handler *async_sigterm_token; void (*after_char_processing_hook) (void); -/* 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, @@ -236,7 +319,7 @@ gdb_rl_callback_handler_install (const char *prompt) 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; }