This is the mail archive of the archer@sourceware.org mailing list for the Archer project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

C++ exceptions catching through GDB-patched .eh_frame


Hi Phil,

the branches discussed are:
archer-pmuldoon-exception-rewind 
	Recover from fatal signals delivered via C++ exceptions in an inferior
	function call
archer-pmuldoon-next-over-throw
	Teach GDB to stop correctly after using a "next" over a throw
	statement 

Illustrative behavior of FSF GDB and Phil's GDB is at the bottom.


Found out now (again; forgot in the call) .eh_frame for
archer-pmuldoon-exception-rewind as I recommended in the call in fact does not
help at all because:

(1) I assumed during an inferior call the unwinder will jump into a catch()
    handler registered even above the dummy frame (the point where the
    inferior call was started).  This is not true, unwinder always stops at
    the dummy frame and does not continue the unwinding (calls
    std::terminate() there).

(2) std::terminate() is a public function (no debuginfo needed):
    $ nm -C --dynamic /usr/lib64/libstdc++.so.6|grep std::terminate
    00000034ddac3d00 T std::terminate()

(3) The method using .eh_frame is a bit complicated that while the .eh_frame
    approach I find more clean the .eh_frame handling can bring more runtime
    difficulties.


But for solving the archer-pmuldoon-next-over-throw problem is some .eh_frame
based solution IMO the only way out.  Some clear facts:

(1) GDB cannot alter the the stack pointer and/or stack content in any way
    before PTRACE_CONTing the inferior as it could alter the inferior
    behavior.

(2) Altering the return address from the current function (where the user
    typed "next") is no help as we can be in a try { } block existing already
    in the current function.  catch { } block in the current function would
    catch the exception thrown somewhere during the execution and GDB would
    lost the control.

(3) GDB cannot alter the return address to the current function as just it is
    created already when the inferior is left running.


.eh_frame section is like .debug_frame section but it has partially different
format (the format is IMO best described by checking the GDB parser
differences against the DWARF-documented .debug_frame format).  .eh_frame is
present in each C++ program even if no debuginfos are installed as .eh_frame
is required for the runtime C++ exceptions unwinding.  One can check the
.eh_frame content using `readelf -Wwf'.  We are interested in `Augmentation:
"...P..."' for the `personality' address (which is contained in `Augmentation
data:' there but this part is not decoded by `readelf').


GDB can find .eh_frame associated with the current function (where "next" is
being run) - it even already has an existing infrastructure for it as it can
unwind frames using .eh_frame (when no .debug_frame is available).  There
always exists such .eh_frame for any called function/library in a C++ program.
And GDB can patch in the `personality' function into this .eh_frame to trap
any attempt to call even a catch { } block located in the current function for
the current try { } block we may be in.  It will need to put the 'P' character
into the Augmentation string of the CIE associated with the FDE for the
current function and also put some address with a GDB breakpoint at the right
position of the Augmentation data at that CIE.

Unfortunately GDB would have to patch the existing in-memory .eh_frame as
registering a new one using inferior call of __register_frame_info() would
have no effect as the existing .eh_frame would be preferred by
gcc/unwind-dw2-fde.c _Unwind_Find_FDE() (`seen_objects' is preferred over
`unseen_objects').

Another problem are recursive functions - if the current function is later
called again, the unwinding trap there should happen for the toplevel function
call.  But this is already being solved during "next" for the placed
breakpoint so this handling needs to be extended even for .eh_frame.



Regards,
Jan


--  Deprecated .eh_frame solution for archer-pmuldoon-exception-rewind: ------
(Probably useless text, just when I wrote it I did not want to drop it.)

GDB already creates a real frame (called "dummy frame") on the inferior stack,
printed by GDB in the examples below as:

#7  <function called from gdb>
It is created in infcall.c:call_function_by_hand().

The problem is that unwinding will stop (I wrongly told skip over in the phone
call) at this dummy frame.  But we can simulate as if we create virtual code:
try {
  call the function requested from GDB
  trap GDB as the function returned (already being done by GDB)
} catch (...) {
  trap GDB as the function threw an exception
}

The `throw' point calls _Unwind_RaiseException() through__cxa_throw().
As _Unwind_RaiseException() returned (it should not) it then called
std::terminate().  The code is at (looked into gcc SVN HEAD sources):
libstdc++-v3/libsupc++/eh_throw.cc -> __cxxabiv1::__cxa_throw()
#4  0x00000034ddac3d13 in std::terminate () at ../../../../libstdc++-v3/libsupc++/eh_terminate.cc:53
#5  0x00000034ddac3dfa in __cxa_throw (obj=<value optimized out>, tinfo=<value optimized out>, dest=<value optimized out>)
    at ../../../../libstdc++-v3/libsupc++/eh_throw.cc:76
#6  0x0000000000400855 in func () at exception.C:6
#7  <function called from gdb>

Called _Unwind_RaiseException() is at: gcc/unwind.inc
It calls _Unwind_RaiseException_Phase2() also there.
This function traverses across all the frames being unwound trying to find the
one which will take care of the exception being thrown.  The bug of FSF GDB is
that no such frame is found and so _URC_FATAL_PHASE2_ERROR is returned for the
dummy frame.
The interesting point is:
      code = uw_frame_state_for (context, &fs);
...
      /* Unwind successful.  Run the personality routine, if any.  */
      if (fs.personality)
        {
          code = (*fs.personality) (1, _UA_CLEANUP_PHASE | match_handler,
                                    exc->exception_class, exc, context);
If GDB can insert artificial address into `fs.personality' it can also place a
breakpoint at this address and the inferior will trap there.  `fs.personality'
gets decoded from the CIE from of the associated `.eh_frame'.  unwind-dw2.c:
      /* "P" indicates a personality routine in the CIE augmentation.  */

One can register artificial .eh_frame by gcc/unwind-dw2-fde.c function
__register_frame_info() (sure as an inferior call from GDB) which will cover
the PC address in the dummy frame.  _Unwind_Find_FDE() there will later find
the registered .eh_frame for PC of the dummy frame.

Sure one needs to call later also __deregister_frame_info().


-- Sample exception.C: -------------------------------------------------------
#include <stdio.h>
 
void
func ()
{
  throw 42;
}

int
main()
{
  setbuf (stdout, NULL);

  try
    {
      func ();		/* line 16 */
      puts ("passed");	/* line 17 */
    }
  catch (...)
    {
      puts ("caught");
    }
  return 0;
}

-- inferior call problem / the goal of archer-pmuldoon-exception-rewind ------
-- This sample output is from FSF GDB. ---------------------------------------
$ gdb -q -nx ./exception
(gdb) b 16
Breakpoint 1 at 0x400871: file exception.C, line 16.
(gdb) r
Starting program: /tmp/exception 

Breakpoint 1, main () at exception.C:16
16	      func ();		/* line 16 */
(gdb) p func ()
terminate called after throwing an instance of 'int'

Program received signal SIGABRT, Aborted.
0x00000034d0c32ed5 in raise (sig=<value optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
64	  return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig);
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on"
Evaluation of the expression containing the function (func()) will be abandoned.
(gdb) bt
#0  0x00000034d0c32ed5 in raise (sig=<value optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1  0x00000034d0c34a43 in abort () at abort.c:88
#2  0x00000034ddac58e4 in __gnu_cxx::__verbose_terminate_handler () at ../../../../libstdc++-v3/libsupc++/vterminate.cc:98
#3  0x00000034ddac3ce6 in __cxxabiv1::__terminate (handler=0x1c0e) at ../../../../libstdc++-v3/libsupc++/eh_terminate.cc:43
#4  0x00000034ddac3d13 in std::terminate () at ../../../../libstdc++-v3/libsupc++/eh_terminate.cc:53
#5  0x00000034ddac3dfa in __cxa_throw (obj=<value optimized out>, tinfo=<value optimized out>, dest=<value optimized out>)
    at ../../../../libstdc++-v3/libsupc++/eh_throw.cc:76
#6  0x0000000000400855 in func () at exception.C:6
#7  <function called from gdb>
#8  main () at exception.C:16
Current language:  auto; currently c
(gdb) c
Continuing.

Program terminated with signal SIGABRT, Aborted.
The program no longer exists.
(gdb) q

-- This sample output is from archer-pmuldoon-exception-rewind. --------------
-- inferior call problem / the goal of archer-pmuldoon-exception-rewind ------
$ gdb -q -nx ./exception
(gdb) b 16
Breakpoint 1 at 0x400871: file exception.C, line 16.
(gdb) r
Starting program: /tmp/exception 

Breakpoint 1, main () at exception.C:16
16	      func ();		/* line 16 */
(gdb) p func ()

Breakpoint 0, std::terminate () at ../../../../libstdc++-v3/libsupc++/eh_terminate.cc:51
51	std::terminate ()
The program being debugged entered a std::terminate call which would
have terminated the program being debugged.  GDB has restored the
context to what it was before the call.
To change this behaviour use "set unwind-on-terminating-exception off"
Evaluation of the expression containing the function (func()) will be abandoned.
(gdb) bt
#0  main () at exception.C:16
(gdb) q

-- `next' problem / the goal of archer-pmuldoon-next-over-throw --------------
-- This sample output is from FSF GDB. ---------------------------------------
$ gdb -q -nx ./exception
(gdb) b 16
Breakpoint 1 at 0x400871: file exception.C, line 16.
(gdb) r
Starting program: /tmp/exception 

Breakpoint 1, main () at exception.C:16
16	      func ();		/* line 16 */
(gdb) next
caught

Program exited normally.
(gdb) q

-- `next' problem / the goal of archer-pmuldoon-next-over-throw --------------
-- This sample output is from archer-pmuldoon-next-over-throw. ---------------
$ gdb -q -nx ./exception
(gdb) b 16
Breakpoint 1 at 0x400871: file exception.C, line 16.
(gdb) r
Starting program: /tmp/exception 

Breakpoint 1, main () at exception.C:16
16	      func ();		/* line 16 */
(gdb) next
0x00000034dba10ef0 in _Unwind_RaiseException (exc=<value optimized out>) from /lib64/libgcc_s.so.1
Current language:  auto; currently c
(gdb) bt
#0  0x00000034dba10ef0 in _Unwind_RaiseException (exc=<value optimized out>) from /lib64/libgcc_s.so.1
#1  0x00000034ddac3ded in __cxa_throw (obj=<value optimized out>, tinfo=<value optimized out>, dest=<value optimized out>)
    at ../../../../libstdc++-v3/libsupc++/eh_throw.cc:71
#2  0x0000000000400855 in func () at exception.C:6
#3  0x0000000000400876 in main () at exception.C:16
(gdb) fini
Run till exit from #0  0x00000034dba10ef0 in _Unwind_RaiseException (exc=<value optimized out>) from /lib64/libgcc_s.so.1
caught

Program exited normally.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]