http://sourceware.org/ml/gdb-cvs/2009-06/msg00076.html gdb/ 2009-06-15 Phil Muldoon * infcall.c (show_unwind_on_terminating_exception_p): New function. (call_function_by_hand): Create breakpoint and clean-up call for std::terminate.breakpoint. Add unwind_on_terminating_exception_p gate. Pop frame on breakpoint hit. (_initialize_infcall): Add add_setshow_boolean_cmd for unwind-on-terminating-exception. gdb/doc/ 2009-06-15 Phil Muldoon * doc/gdb.texinfo (Calling): Document set-unwind-on-terminating-exception usage. gdb/testsuite/ 2009-06-15 Phil Muldoon * gdb.cp/gdb2495.cc: New file. * gdb.cp/gdb2495.exp: New file. --- src/gdb/infcall.c 2009/05/28 00:53:51 1.114 +++ src/gdb/infcall.c 2009/06/15 12:11:36 1.115 @@ -98,6 +98,30 @@ value); } +/* This boolean tells what gdb should do if a std::terminate call is + made while in a function called from gdb (call dummy). + As the confines of a single dummy stack prohibit out-of-frame + handlers from handling a raised exception, and as out-of-frame + handlers are common in C++, this can lead to no handler being found + by the unwinder, and a std::terminate call. This is a false positive. + If set, gdb unwinds the stack and restores the context to what it + was before the call. + + The default is to unwind the frame if a std::terminate call is + made. */ + +static int unwind_on_terminating_exception_p = 1; + +static void +show_unwind_on_terminating_exception_p (struct ui_file *file, int from_tty, + struct cmd_list_element *c, + const char *value) + +{ + fprintf_filtered (file, _("\ +Unwind stack if a C++ exception is unhandled while in a call dummy is %s.\n"), + value); +} /* Perform the standard coercions that are specified for arguments to be passed to C or Ada functions. @@ -416,6 +440,8 @@ struct cleanup *args_cleanup; struct frame_info *frame; struct gdbarch *gdbarch; + struct breakpoint *terminate_bp = NULL; + struct minimal_symbol *tm; ptid_t call_thread_ptid; struct gdb_exception e; const char *name; @@ -716,6 +742,27 @@ bpt->disposition = disp_del; } + /* Create a breakpoint in std::terminate. + If a C++ exception is raised in the dummy-frame, and the + exception handler is (normally, and expected to be) out-of-frame, + the default C++ handler will (wrongly) be called in an inferior + function call. This is wrong, as an exception can be normally + and legally handled out-of-frame. The confines of the dummy frame + prevent the unwinder from finding the correct handler (or any + handler, unless it is in-frame). The default handler calls + std::terminate. This will kill the inferior. Assert that + terminate should never be called in an inferior function + call. Place a momentary breakpoint in the std::terminate function + and if triggered in the call, rewind. */ + if (unwind_on_terminating_exception_p) + { + struct minimal_symbol *tm = lookup_minimal_symbol ("std::terminate()", + NULL, NULL); + if (tm != NULL) + terminate_bp = set_momentary_breakpoint_at_pc + (SYMBOL_VALUE_ADDRESS (tm), bp_breakpoint); + } + /* Everything's ready, push all the info needed to restore the caller (and identify the dummy-frame) onto the dummy-frame stack. */ @@ -726,6 +773,10 @@ or discard it. */ discard_cleanups (inf_status_cleanup); + /* Register a clean-up for unwind_on_terminating_exception_breakpoint. */ + if (terminate_bp) + make_cleanup_delete_breakpoint (terminate_bp); + /* - SNIP - SNIP - SNIP - SNIP - SNIP - SNIP - SNIP - SNIP - SNIP - If you're looking to implement asynchronous dummy-frames, then just below is the place to chop this function in two.. */ @@ -881,6 +932,38 @@ if (!stop_stack_dummy) { + + /* Check if unwind on terminating exception behaviour is on. */ + if (unwind_on_terminating_exception_p) + { + /* Check that the breakpoint is our special std::terminate + breakpoint. If it is, we do not want to kill the inferior + in an inferior function call. Rewind, and warn the + user. */ + + if (terminate_bp != NULL + && (inferior_thread()->stop_bpstat->breakpoint_at->address + == terminate_bp->loc->address)) + { + /* We must get back to the frame we were before the + dummy call. */ + dummy_frame_pop (dummy_id); + + /* We also need to restore inferior status to that before the + dummy call. */ + restore_inferior_status (inf_status); + + error (_("\ +The program being debugged entered a std::terminate call, most likely\n\ +caused by an unhandled C++ exception. GDB blocked this call in order\n\ +to prevent the program from being terminated, and has restored the\n\ +context to its original state before the call.\n\ +To change this behaviour use \"set unwind-on-terminating-exception off\".\n\ +Evaluation of the expression containing the function (%s)\n\ +will be abandoned."), + name); + } + } /* We hit a breakpoint inside the FUNCTION. Keep the dummy frame, the user may want to examine its state. Discard inferior status, we're not at the same point @@ -989,4 +1072,19 @@ NULL, show_unwind_on_signal_p, &setlist, &showlist); + + add_setshow_boolean_cmd ("unwind-on-terminating-exception", no_class, + &unwind_on_terminating_exception_p, _("\ +Set unwinding of stack if std::terminate is called while in call dummy."), _("\ +Show unwinding of stack if std::terminate() is called while in a call dummy."), _("\ +The unwind on terminating exception flag lets the user determine\n\ +what gdb should do if a std::terminate() call is made from the\n\ +default exception handler. If set, gdb unwinds the stack and restores\n\ +the context to what it was before the call. If unset, gdb allows the\n\ +std::terminate call to proceed.\n\ +The default is to unwind the frame."), + NULL, + show_unwind_on_terminating_exception_p, + &setlist, &showlist); + } --- src/gdb/doc/gdb.texinfo 2009/06/11 11:57:46 1.599 +++ src/gdb/doc/gdb.texinfo 2009/06/15 12:11:36 1.600 @@ -12895,6 +12895,16 @@ the function, or if you passed it incorrect arguments). What happens in that case is controlled by the @code{set unwindonsignal} command. +Similarly, with a C@t{++} program it is possible for the function you +call via the @code{print} or @code{call} command to generate an +exception that is not handled due to the constraints of the dummy +frame. In this case, any exception that is raised in the frame, but has +an out-of-frame exception handler will not be found. GDB builds a +dummy-frame for the inferior function call, and the unwinder cannot +seek for exception handlers outside of this dummy-frame. What happens +in that case is controlled by the +@code{set unwind-on-terminating-exception} command. + @table @code @item set unwindonsignal @kindex set unwindonsignal @@ -12911,6 +12921,23 @@ @kindex show unwindonsignal Show the current setting of stack unwinding in the functions called by @value{GDBN}. + +@item set unwind-on-terminating-exception +@kindex set unwind-on-terminating-exception +@cindex unwind stack in called functions with unhandled exceptions +@cindex call dummy stack unwinding on unhandled exception. +Set unwinding of the stack if a C@t{++} exception is raised, but left +unhandled while in a function that @value{GDBN} called in the program being +debugged. If set to on (the default), @value{GDBN} unwinds the stack +it created for the call and restores the context to what it was before +the call. If set to off, @value{GDBN} the exception is delivered to +the default C@t{++} exception handler and the inferior terminated. + +@item show unwind-on-terminating-exception +@kindex show unwind-on-terminating-exception +Show the current setting of stack unwinding in the functions called by +@value{GDBN}. + @end table @cindex weak alias functions --- src/gdb/testsuite/gdb.cp/gdb2495.cc +++ src/gdb/testsuite/gdb.cp/gdb2495.cc 2009-06-16 12:49:45.874202000 +0000 @@ -0,0 +1,89 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 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 . + */ + +#include +#include + +using namespace std; + +class SimpleException +{ + +public: + + void raise_signal (int dummy) + { + if (dummy > 0) + raise(SIGABRT); + } + + int no_throw_function () + { + return 1; + } + + void throw_function () + { + throw 1; + } + + int throw_function_with_handler () + { + try + { + throw 1; + } + catch (...) + { + cout << "Handled" << endl; + } + + return 2; + } + + void call_throw_function_no_handler () + { + throw_function (); + } + + void call_throw_function_handler () + { + throw_function_with_handler (); + } +}; +SimpleException exceptions; + +int +main() +{ + /* Have to call these functions so GCC does not optimize them + away. */ + exceptions.raise_signal (-1); + exceptions.no_throw_function (); + exceptions.throw_function_with_handler (); + exceptions.call_throw_function_handler (); + try + { + exceptions.throw_function (); + exceptions.call_throw_function_no_handler (); + } + catch (...) + { + } + return 0; +} --- src/gdb/testsuite/gdb.cp/gdb2495.exp +++ src/gdb/testsuite/gdb.cp/gdb2495.exp 2009-06-16 12:49:46.889492000 +0000 @@ -0,0 +1,157 @@ +# Copyright 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 . + + +# In gdb inferior function calls, if a C++ exception is raised in the +# dummy-frame, and the exception handler is (normally, and expected to +# be) out-of-frame, the default C++ handler will (wrongly) be called +# in an inferior function call. +# This is incorrect as an exception can normally and legally be handled +# out-of-frame. The confines of the dummy frame prevent the unwinder +# from finding the correct handler (or any handler, unless it is +# in-frame). The default handler calls std::terminate. This will kill +# the inferior. Assert that terminate should never be called in an +# inferior function call. These tests test the functionality around +# unwinding that sequence and also tests the flag behaviour gating this +# functionality. + +# This test is largely based of gdb.base/callfuncs.exp. + +if $tracelevel then { + strace $tracelevel +} + +if { [skip_cplus_tests] } { continue } + +set prms_id 2495 +set bug_id 0 + +set testfile "gdb2495" +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++"] { + return -1 +} + +if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug c++}] != "" } { + untested gdb2495.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 "*-*-*" 2416 + 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 http://sourceware.org/gdb/bugs/2495 + +# Test normal baseline behaviour. Call a function that +# does not raise an exception. +gdb_test "p exceptions.no_throw_function()" " = 1" +# And one that does but handles it in-frame. +gdb_test "p exceptions.throw_function_with_handler()" " = 2" +# Both should return normally. + +# Test basic unwind. Call a function that raises an exception but +# does not handle it. It should be rewound. +gdb_test "p exceptions.throw_function()" \ + "The program being debugged entered a std::terminate call, .*" \ + "Call a function that raises an exception without a handler." + +# Make sure that after rewinding we are back at the call parent. +gdb_test "bt" \ + "#0 main.*" \ + "bt after returning from a popped frame" + +# Make sure the only breakpoint is the one set via the runto_main +# call and that the std::terminate breakpoint has evaporated and +# cleaned-up. +gdb_test "info breakpoints" \ + "gdb.cp/gdb2495\.cc.*" + +# Turn off this new behaviour. +gdb_test_multiple "set unwind-on-terminating-exception off" \ + "Turn unwind-on-terminating-exception off" { + -re "$gdb_prompt $" {pass "set unwinn-on-terminating-exception off"} + timeout {fail "(timeout) set unwind-on-terminating-exception off"} +} + +# Check that it is turned off. +gdb_test "show unwind-on-terminating-exception" \ + "exception is unhandled while in a call dummy is off.*" \ + "Turn off unwind on terminating exception flag" + +# Check that the old behaviour is restored. +gdb_test "p exceptions.throw_function()" \ + "The program being debugged was signaled while in a function called .*" \ + "Call a function that raises an exception with unwinding off.." + +# Restart the inferior back at main. +if ![runto_main] then { + perror "couldn't run to main" + continue +} + + +# Check to see if the new behaviour alters the unwind signal +# behaviour; it should not. Test both on and off states. + +# Turn on unwind on signal behaviour. +gdb_test_multiple "set unwindonsignal on" "Turn unwindonsignal on" { + -re "$gdb_prompt $" {pass "set unwindonsignal on"} + timeout {fail "(timeout) set unwindonsignal on"} +} + +# Check that it is turned on. +gdb_test "show unwindonsignal" \ + "signal is received while in a call dummy is on.*" \ + "Turn on unwind on signal" + +# Check to see if new behaviour interferes with +# normal signal handling in inferior function calls. +gdb_test "p exceptions.raise_signal(1)" \ + "To change this behavior use \"set unwindonsignal off\".*" + +# And reverse - turn off again. +gdb_test_multiple "set unwindonsignal off" "Turn unwindonsignal off" { + -re "$gdb_prompt $" {pass "set unwindonsignal off"} + timeout {fail "(timeout) set unwindonsignal off"} +} + +# Check that it is actually turned off. +gdb_test "show unwindonsignal" \ + "signal is received while in a call dummy is off.*" \ + "Turn off unwind on signal" + +# Check to see if new behaviour interferes with +# normal signal handling in inferior function calls. +gdb_test "p exceptions.raise_signal(1)" \ + "To change this behavior use \"set unwindonsignal on\".*" --- src/gdb/testsuite/gdb.cp/Makefile.in 2009/02/03 01:09:01 1.5 +++ src/gdb/testsuite/gdb.cp/Makefile.in 2009/06/15 12:11:37 1.6 @@ -4,7 +4,7 @@ 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 pr9594 + ref-types ref-params method2 pr9594 gdb2495 all info install-info dvi install uninstall installcheck check: @echo "Nothing to be done for $@..."