Discussion:
using backtrace() in exception throwing?
Noel Grandin
2013-02-17 15:35:46 UTC
Permalink
Hi

Background: I'm looking at bug reports, and the messages are generally only
marginally useful, because I generally have no idea where the original
exception was actually thrown from.
Sometimes the message in the exception will narrow it down a little, but
that still often leaves a lot of likely places, and often the information
I'm really interested in, is a few frames worth of callng stack information.

What would be the costs of calling backtrace()
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
in the exception base class, storing a few frames worth of data,
and then modifying the exception printing code to call
backtrace_symbols()
when writing out the exception?

For me personally, this would really speed up tracking down the source of
bugs.

Regards, Noel Grandin
Lubos Lunak
2013-02-18 13:39:43 UTC
Permalink
Post by Noel Grandin
Hi
Background: I'm looking at bug reports, and the messages are generally only
marginally useful, because I generally have no idea where the original
exception was actually thrown from.
Sometimes the message in the exception will narrow it down a little, but
that still often leaves a lot of likely places, and often the information
I'm really interested in, is a few frames worth of callng stack information.
What would be the costs of calling backtrace()
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
in the exception base class, storing a few frames worth of data,
and then modifying the exception printing code to call
backtrace_symbols()
when writing out the exception?
For me personally, this would really speed up tracking down the source of
bugs.
This could be very useful ('catch throw' is so cumbersome in gdb), but I
think the first thing to check is if it would actually work. Last time I
checked, "recent" features like -fvisibility make backtrace() very often
generate call traces that are next to useless.

If it turns out the data actually is useful, then also

- you can't simply modify the Exception base class, because it's part of the
URE, so it should stay binary compatible. It'd need to be stored in the
message (which is a question if it's a good or bad idea) or some other way
would need to be found.

- it'd be good to know how fast/slow the function is. Exceptions generally
aren't very fast, but if the function was called every time in the ctor, then
depending on fast it is that might make it debug-build-only feature.
--
Lubos Lunak
***@suse.cz
Noel Grandin
2013-02-18 13:44:31 UTC
Permalink
Post by Lubos Lunak
This could be very useful ('catch throw' is so cumbersome in gdb), but
I think the first thing to check is if it would actually work. Last
time I checked, "recent" features like -fvisibility make backtrace()
very often generate call traces that are next to useless. If it turns
out the data actually is useful, then also - you can't simply modify
the Exception base class, because it's part of the URE, so it should
stay binary compatible. It'd need to be stored in the message (which
is a question if it's a good or bad idea) or some other way would need
to be found. - it'd be good to know how fast/slow the function is.
Exceptions generally aren't very fast, but if the function was called
every time in the ctor, then depending on fast it is that might make
it debug-build-only feature.
Thanks for the feedback.

I'll have a go at it and report back.

Disclaimer: http://www.peralex.com/disclaimer.html
Tom Tromey
2013-02-18 17:09:41 UTC
Permalink
Lubos> This could be very useful ('catch throw' is so cumbersome in
Lubos> gdb),

Is there something we could do to improve it?

Lubos> but I think the first thing to check is if it would
Lubos> actually work. Last time I checked, "recent" features like
Lubos> -fvisibility make backtrace() very often generate call traces
Lubos> that are next to useless.

There are a few other approaches that are possible on Linux.

There is one that is part of GCC, libbacktrace.
It is its own library, but part of the GCC tree; so you'd have to
extract it.

Jan is also writing one based on elfutils. It is in elfutils git but
not merged to master yet.

Also it can be done using the existing unwinder data, which already
exists for exception handling. This is the approach libgcj took.
Specifically, it used this data to construct the list of PC values and
stash this in the exception; this was then transformed when actually
printing an exception. The code in libgcj is a bit hairier than you
might need, since it had to handle interpreter and libffi frames
specially. You could probably adapt it.

Tom
Noel Grandin
2013-02-18 17:18:34 UTC
Permalink
Post by Tom Tromey
Lubos> This could be very useful ('catch throw' is so cumbersome in
Lubos> gdb),
Is there something we could do to improve it?
Would be nice if gdb could actually do "catch throw XXXException" like the
documentation says - perhaps the documentation was modified prematurely?

http://stackoverflow.com/questions/6835728/gdb-how-to-break-when-a-specific-exception-type-is-thrown
Tom Tromey
2013-02-18 17:30:49 UTC
Permalink
Noel> Would be nice if gdb could actually do "catch throw XXXException" like
Noel> the documentation says - perhaps the documentation was modified
Noel> prematurely?

Yeah, I think it was.
The docs have been changed to reflect reality.
Actually implementing the feature is on our to-do list:
http://sourceware.org/bugzilla/show_bug.cgi?id=13588

Tom
Noel Grandin
2013-02-19 08:13:15 UTC
Permalink
Post by Tom Tromey
Noel> Would be nice if gdb could actually do "catch throw XXXException" like
Noel> the documentation says - perhaps the documentation was modified
Noel> prematurely?
Yeah, I think it was.
The docs have been changed to reflect reality.
http://sourceware.org/bugzilla/show_bug.cgi?id=13588
Cool.

While we're on the subject of GDB improvements, I personally would find
a "step return" command very useful.
i.e. continue until we leave the current stack frame
very useful when you've stepped into something and you realise this
method is useless, and you just want to continue until you're one method
up again.

Disclaimer: http://www.peralex.com/disclaimer.html
Miklos Vajna
2013-02-19 08:23:10 UTC
Permalink
Post by Noel Grandin
While we're on the subject of GDB improvements, I personally would
find a "step return" command very useful.
i.e. continue until we leave the current stack frame
very useful when you've stepped into something and you realise this
method is useless, and you just want to continue until you're one
method up again.
Are you speaking about 'finish'?
Noel Grandin
2013-02-19 08:38:51 UTC
Permalink
Post by Miklos Vajna
Post by Noel Grandin
While we're on the subject of GDB improvements, I personally would
find a "step return" command very useful.
i.e. continue until we leave the current stack frame
very useful when you've stepped into something and you realise this
method is useless, and you just want to continue until you're one
method up again.
Are you speaking about 'finish'?
Ah, indeed. Thanks.


Disclaimer: http://www.peralex.com/disclaimer.html
Lubos Lunak
2013-02-19 13:52:51 UTC
Permalink
Post by Tom Tromey
Lubos> This could be very useful ('catch throw' is so cumbersome in
Lubos> gdb),
Is there something we could do to improve it?
I don't know how much control gdb over exception handling has, so I don't
know :).

What I was refering to was the problem that if a catch block catches an
exception, it's often difficult to find out where it actually came from.
Using 'catch catch' doesn't show where it originated (unless I missed a
non-obvious way). And if the exception propagated out of complex nesting of
function calls, then 'catch throw' may trigger a number of times for
exceptions that will be handled elsewhere.

So it would be useful to have some kind of 'catch throw-for-this-catch', or
at least some 'show exception' (mentioned in 'help catch') that would show
where the currently propagating exception started.
--
Lubos Lunak
***@suse.cz
Tom Tromey
2013-02-20 21:28:06 UTC
Permalink
Tom> Is there something we could do to improve it?

Lubos> I don't know how much control gdb over exception handling has,
Lubos> so I don't know :).

:-)

FWIW we have the same problem in reverse: the gdb group at Red Hat is,
among other things, tasked with improving the C++ debugging experience.
However, most of us don't actually debug C++ programs on a regular
basis. We do know some issues, via bugzilla and other discussions, but
I feel sure we are also missing things.

Lubos> What I was refering to was the problem that if a catch block
Lubos> catches an exception, it's often difficult to find out where it
Lubos> actually came from. Using 'catch catch' doesn't show where it
Lubos> originated (unless I missed a non-obvious way). And if the
Lubos> exception propagated out of complex nesting of function calls,
Lubos> then 'catch throw' may trigger a number of times for exceptions
Lubos> that will be handled elsewhere.

Solving this in general looks tricky to me. I am not really sure how to
do it, but I will think about it some more.

The basic issue is that if 'catch throw' triggers multiple times for the
same exception, then it seems that there must be code that catches the
exception and then throws it again:

try { } catch (blah) { throw blah; }

As opposed to a true re-throw:

try { } catch (blah) { throw; }

AFAIK re-throws are currently not caught, see
http://sourceware.org/bugzilla/show_bug.cgi?id=12824

I'm not sure whether it is possible to easily detect whether "throw x"
is throwing some object which has already been thrown.

Hopefully I'm misunderstanding the problem :)


Meanwhile, I did whip up a quick-and-dirty Python-based approach. It
adds a new "track-throws" command. This command installs a breakpoint
that records the point of the most recent "throw". Then you can examine
the result with "info last-throw".

Here it is in action:

(gdb) source track_throw.py
(gdb) track-throws
Breakpoint 1 at 0x400910
(gdb) catch catch
Catchpoint 2 (catch)
(gdb) run
[...]
Catchpoint 2 (exception caught), __cxxabiv1::__cxa_begin_catch (
exc_obj_in=0x602070) at ../../../../libstdc++-v3/libsupc++/eh_catch.cc:41
41 {
(gdb) info last-throw
Last exception thrown at file ../../../archer/gdb/testsuite/gdb.cp/nextoverthrow.cc, line 36


"track-throws" makes the breakpoint it installs user-visible so you can
disable the feature simply by deleting the breakpoint.

I'm curious to know if this is useful to you.

Tom


import gdb

last_sal = None

throw_bp = None

class ThrowTracker(gdb.Breakpoint):
def __init__(self):
gdb.Breakpoint.__init__(self, '__cxa_throw')

def stop(self):
global last_sal
frame = gdb.newest_frame().older()
last_sal = frame.find_sal()
return False

class TrackThrows(gdb.Command):
def __init__(self):
gdb.Command.__init__(self, 'track-throws', gdb.COMMAND_BREAKPOINTS)

def invoke(self, arg, from_tty):
global throw_bp
if throw_bp is None or not throw_bp.is_valid():
# Still no good way to create a pending breakpoint from
# Python.
save = gdb.parameter('breakpoint pending')
gdb.execute('set breakpoint pending on', to_string = True)
throw_bp = ThrowTracker()
if save is None:
arg = 'auto'
elif save:
arg = 'on'
else:
arg = 'off'
gdb.execute('set breakpoint pending %s' % arg, to_string = True)

class InfoThrow(gdb.Command):
def __init__(self):
gdb.Command.__init__(self, 'info last-throw', gdb.COMMAND_BREAKPOINTS)

def invoke(self, arg, from_tty):
global last_sal
if last_sal is not None:
filename = last_sal.symtab.filename
line = last_sal.line
print "Last exception thrown at file %s, line %d" % (filename, line)
else:
print "No previous exception seen"

TrackThrows()
InfoThrow()
Michael Meeks
2013-02-22 12:46:49 UTC
Permalink
Hi Tom,
Post by Tom Tromey
FWIW we have the same problem in reverse: the gdb group at Red Hat is,
among other things, tasked with improving the C++ debugging experience.
However, most of us don't actually debug C++ programs on a regular
basis. We do know some issues, via bugzilla and other discussions, but
I feel sure we are also missing things.
Oh wow :-) so Lubos' feedback here is really great; personally I feel
like rather an inadequate gdb user myself ;-)
Post by Tom Tromey
Solving this in general looks tricky to me. I am not really sure how to
do it, but I will think about it some more.
The basic debugging experience in these "an exception broke something"
flows is that we get an exception thrown that ultimately ends up in a
pathalogical situation - an abort, or some similar horrible badness. At
that point the most interesting thing is not the catcher - which usually
ends up being utterly random - but the last guy that threw the
exception. So then as Lubos says comes the knotty job of trying to put a
breakpoint on the -one- exception that ends up being caught where we are
now [ and that of course requires re-running, and inevitably we throw
dozens of exceptions in the normal case ].
Post by Tom Tromey
Meanwhile, I did whip up a quick-and-dirty Python-based approach. It
adds a new "track-throws" command. This command installs a breakpoint
that records the point of the most recent "throw". Then you can examine
the result with "info last-throw".
This of course goes a huge way to solving the above problem :-)

Really nice ! though of course - having a full stack trace would make
that very substantially more useful.

Even better than this would (perhaps) be a "break inside thrower that
is caught here" type breakpoint - that we could invoke to land us in
whatever code is going to throw as it does that [ and before it started
all the magic cleanup / unwinding work ]. That is - assuming that it's
possible for the code to know (at that point) where it will ultimately
end up (? ;-)

Anyhow - it's great to have some python help here; I guess we could
bundle that into our existing grab-bag of nice python fixes today :-)

Thanks !

Michael.
--
***@suse.com <><, Pseudo Engineer, itinerant idiot
Tom Tromey
2013-02-22 18:14:19 UTC
Permalink
Michael> The basic debugging experience in these "an exception broke
Michael> something" flows is that we get an exception thrown that
Michael> ultimately ends up in a pathalogical situation - an abort, or
Michael> some similar horrible badness. At that point the most
Michael> interesting thing is not the catcher - which usually ends up
Michael> being utterly random - but the last guy that threw the
Michael> exception. So then as Lubos says comes the knotty job of trying
Michael> to put a breakpoint on the -one- exception that ends up being
Michael> caught where we are now [ and that of course requires
Michael> re-running, and inevitably we throw dozens of exceptions in the
Michael> normal case ].

Thanks. This kind of discussion is very helpful to me.

This problem is a bit tricky.

The various low-level exception-related functions, like __cxa_throw,
treat the exception object as a "void *". However, the value of this
seems to change depending on the "throw" point. It's clear that this
can't always be the argument to throw, due to scalar and object throws.
So I wonder what exactly it refers to. I'll have to dig a bit deeper to
see how all this code really works.

Anyway, this makes tracking backward from std::terminate to the original
throw point more difficult.


It helps a bit to install the libstdc++ debuginfo. Then at least you
can dig into some of the details. However, due to optimization, even
with the improvements in newer version of gcc, this turns out to be less
than perfect.


I tried this out to see what it was like. It is kind of awful! At
least, I had to dig around through several frames of libstdc++ to find
the object that lead to std::terminate being called:

terminate called after throwing an instance of 'char const*'

Program received signal SIGABRT, Aborted.
0x0000003be3036285 in __GI_raise (sig=6)
at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
64 return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig);
(gdb) up
#1 0x0000003be3037b9b in __GI_abort () at abort.c:91
91 raise (SIGABRT);
(gdb) up
#2 0x0000003be80bbc5d in __gnu_cxx::__verbose_terminate_handler ()
at ../../../../libstdc++-v3/libsupc++/vterminate.cc:95
95 abort();
(gdb) up
#3 0x0000003be80b9e16 in __cxxabiv1::__terminate (handler=<optimized out>)
at ../../../../libstdc++-v3/libsupc++/eh_terminate.cc:40
40 handler ();
(gdb) up
#4 0x0000003be80b9e43 in std::terminate ()
at ../../../../libstdc++-v3/libsupc++/eh_terminate.cc:50
50 __terminate (__terminate_handler);
(gdb) up
#5 0x0000003be80b9f3e in __cxxabiv1::__cxa_throw (obj=0x601090,
tinfo=<optimized out>, dest=<optimized out>)
at ../../../../libstdc++-v3/libsupc++/eh_throw.cc:83
83 std::terminate ();


At this point I can do:

(gdb) catch throw
Catchpoint 1 (throw)
(gdb) cond 1 obj == 0x601090
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
[Inferior 25299 exited]
Starting program: /tmp/r
warning: failed to reevaluate condition for breakpoint 1: No symbol "obj" in current context.
warning: failed to reevaluate condition for breakpoint 1: No symbol "obj" in current context.
warning: failed to reevaluate condition for breakpoint 1: No symbol "obj" in current context.
Catchpoint 1 (exception thrown), __cxxabiv1::__cxa_throw (obj=0x601090, tinfo=
0x600a60, dest=0) at ../../../../libstdc++-v3/libsupc++/eh_throw.cc:70
70 header->exc.unexpectedHandler = __unexpected_handler;


Ignore the warnings; I'm not sure what they are about, but I will file a
bug.

... but all this still fails if you insert a "manual re-throw" like
"throw x;" into the call chain. At that point it gets really messy :(


Michael> Really nice ! though of course - having a full stack trace
Michael> would make that very substantially more useful.

This is reasonably easy to implement. It may be expensive. I've
appended a version that does this... well, it lists file name and line
number for all the frames. If you want to get a really full stack
trace, capturing the arguments and locals, then you would have to do
more work.

Michael> Even better than this would (perhaps) be a "break inside thrower that
Michael> is caught here" type breakpoint - that we could invoke to land us in
Michael> whatever code is going to throw as it does that [ and before it started
Michael> all the magic cleanup / unwinding work ]. That is - assuming that it's
Michael> possible for the code to know (at that point) where it will ultimately
Michael> end up (? ;-)

I think it isn't possible in general. When an exception is thrown, I
think all that can really be determined is the next catch point.

What this means is that if you have a series of throws and re-throws,
winding up at some "catch", then the best you could do is stop at the
re-throw that leads to that catch.

Does that make sense?

Like:

void doit()
{
throw "hi";
}

void dd2()
{
try {
doit();
} catch (const char *x) {
throw x;
}
}

int main()
{
try {
dd2();
} catch (const char *x) {
// The problem spot.
}
}

Here, suppose the comment marks the catch you are concerned with.
You want to find the throw that leads to this point.

The original throw in "doit" can only see as far as the catchpoint in
"dd2". That is because arbitrary code can be run there -- for example
it may swallow the exception and no more throwing is even done.

So this hypothetical breakpoint would only trigger at the re-throw in
dd2.

But, that isn't what you want. And from there it is even hard to track
backwards, you have to "catch catch" and filter for the particular one
you want.



I'm curious what types of exceptions are actually thrown in LibreOffice.
Does it throw -- scalars? Objects? Just pointers ("Java style")?


A few improvements come to mind. I'd like to hear your take on these,
or any other ideas you've got.


It seems like it would be nice if gdb exposed some kind of convenience
variable so that "catch catch" and "catch throw" could be conditional on
the thrown object without needing the libstdc++ debuginfo.

This may require some libstdc++ change, perhaps a probe point.


It would be nice to solve the problem above, of following an exception
back to its ultimate origination point. It seems like this would be
useful even if it were not 100% reliable, in the sense that it is ok to
have a few extra breakpoints -- filtering 90% of uninteresting
exceptions is better than filtering 0% of them.

If we had the convenience variable mentioned above, and if LibreOffice
has a relatively simple "exception identity" measure (e.g., if you only
throw pointers, you can just compare them with ==), then it could
perhaps be done by: break at the losing catch, make a conditional "catch
throw", then re-run.

Tom

import gdb

last_sals = None

throw_bp = None

class ThrowTracker(gdb.Breakpoint):
def __init__(self):
gdb.Breakpoint.__init__(self, '__cxa_throw')

def stop(self):
global last_sals
frame = gdb.newest_frame().older()
last_sals = []
while frame is not None:
last_sals.append(frame.find_sal())
frame = frame.older()
return False

class TrackThrows(gdb.Command):
def __init__(self):
gdb.Command.__init__(self, 'track-throws', gdb.COMMAND_BREAKPOINTS)

def invoke(self, arg, from_tty):
global throw_bp
if throw_bp is None or not throw_bp.is_valid():
# Still no good way to create a pending breakpoint from
# Python.
save = gdb.parameter('breakpoint pending')
gdb.execute('set breakpoint pending on', to_string = True)
throw_bp = ThrowTracker()
if save is None:
arg = 'auto'
elif save:
arg = 'on'
else:
arg = 'off'
gdb.execute('set breakpoint pending %s' % arg, to_string = True)

class InfoThrow(gdb.Command):
def __init__(self):
gdb.Command.__init__(self, 'info last-throw', gdb.COMMAND_BREAKPOINTS)

def invoke(self, arg, from_tty):
global last_sals
if last_sals is not None:
first = True
for sal in last_sals:
filename = sal.symtab.filename
line = sal.line
if first:
print "Last exception thrown at file %s, line %d" % (filename, line)
first = False
else:
print "From %s, %d" % (filename, line)
else:
print "No previous exception seen"

TrackThrows()
InfoThrow()
Stephan Bergmann
2013-02-26 08:00:00 UTC
Permalink
Post by Tom Tromey
I'm curious what types of exceptions are actually thrown in LibreOffice.
Does it throw -- scalars? Objects? Just pointers ("Java style")?
LibreOffice's UNO component system has a large exception hierarchy,
which is modeled as a hierarchy of classes in C++. So what is thrown is
typically (copies of) stack-allocated class instances, of specific
sub-classes of com::sun::star::uno::Exception.
Post by Tom Tromey
If we had the convenience variable mentioned above, and if LibreOffice
has a relatively simple "exception identity" measure (e.g., if you only
throw pointers, you can just compare them with ==), then it could
perhaps be done by: break at the losing catch, make a conditional "catch
throw", then re-run.
Overall execution in LibreOffice is likely dynamic enough that the
relevant exception class instance has different addresses in different runs.

Stephan
Jan Holesovsky
2013-02-26 13:23:17 UTC
Permalink
Hi Tom,
Post by Tom Tromey
Michael> Even better than this would (perhaps) be a "break inside thrower that
Michael> is caught here" type breakpoint - that we could invoke to land us in
Michael> whatever code is going to throw as it does that [ and before it started
Michael> all the magic cleanup / unwinding work ]. That is - assuming that it's
Michael> possible for the code to know (at that point) where it will ultimately
Michael> end up (? ;-)
I think it isn't possible in general. When an exception is thrown, I
think all that can really be determined is the next catch point.
What this means is that if you have a series of throws and re-throws,
winding up at some "catch", then the best you could do is stop at the
re-throw that leads to that catch.
Even if this is not solving everything / the general case, I think this
would still be pretty useful for LibreOffice - I don't remember being
bitten by rethrows when debugging LO problems. And always one could set
this thing [how to call it, actually? ;-)] where the re-throw happens,
and try again - still it would save quite some time compared to first
setting a breakpoint to get to the relevant piece of code + catch throw
+ catch catch + hope that they are the right ones.
Post by Tom Tromey
Here, suppose the comment marks the catch you are concerned with.
You want to find the throw that leads to this point.
The original throw in "doit" can only see as far as the catchpoint in
"dd2". That is because arbitrary code can be run there -- for example
it may swallow the exception and no more throwing is even done.
So this hypothetical breakpoint would only trigger at the re-throw in
dd2.
In other words - I'd love if gdb were able to do this even with the
re-throw limitation :-)

All the best,
Kendy
Michael Meeks
2013-03-13 12:00:47 UTC
Permalink
Hi Tom,
Post by Jan Holesovsky
Post by Tom Tromey
Michael> Even better than this would (perhaps) be a "break inside thrower that
Michael> is caught here" type breakpoint - that we could invoke to land us in
Michael> whatever code is going to throw as it does that [ and before it started
Michael> all the magic cleanup / unwinding work ]. That is - assuming that it's
Michael> possible for the code to know (at that point) where it will ultimately
Michael> end up (? ;-)
I think it isn't possible in general. When an exception is thrown, I
think all that can really be determined is the next catch point.
This would be perfect. Explicit rethrows are relatively rare in our
code, however it is completely normal to have code that we simply don't
understand and/or follow - whereby we do:

A:
try {
B: do_incredibly_complex_thing();
} catch (const uno::Exception &e) {
C: ... flow eventually ends up here ...
}

Where by do_incredibly_complex_thing - it is expected that this method
dlopens a dozen separate shared libraries, does 1bn instructions or so -
and (somewhere in the middle) fails in some odd way - that we're trying
to find. Oh - of course, about two dozen+ 'expected' exceptions will get
thrown and caught during that process.

Current approaches to trying to find: "what inside B threw to C"
involve: putting a break-point in 'A', then putting a breakpoint in
__cxa_throw - continuing, and typing 'continue <N>' several times to
work out what value of 'two dozen+' we have today, manually counting /
trying to binary chop towards that number: and finally we get near
enough that breaking on and carefully examining every exception in a
plausible range that may cause this issue.

That flow as you can see is pretty hideous :-) what we really want to
do is:

catch thrower-to-C

or somesuch :-) being able to handle rethrows simply by re-starting gdb
and the app, doing a 'catch thrower-to-<rethrow-catch>' would be
blissful in comparison - walking up the chain one by one ;-)
Post by Jan Holesovsky
In other words - I'd love if gdb were able to do this even with the
re-throw limitation :-)
As kendy says this would really help us read, understand, unwind and
improve complex code.
Post by Jan Holesovsky
catch catch [REGEXP]
catch throw [REGEXP]
catch rethrow [REGEXP]
This is really nice; the ability to hide many of the "two dozen+"
expected exceptions in some way be really useful. Unfortunately these
are often of quite generic types :-)

Tor suggested on IRC some way of ignoring specific exception throwing
sites which tend to creep into the code over time and need tackling.
Some low-level technology will decide it's better to fire an Exception
than return an undefined value of some kind - and suddenly two-dozen+
turns into 100's - (I just got this today) ;-)

Being able to say: catch throw ignore - which would ignore the last
thrown exception site would be really lovely ;-)

Is any of that useful ? - really looking forward to getting a new
libstdc++ etc. with your fixes in a few months :-)

Thanks !

Michael.
--
***@suse.com <><, Pseudo Engineer, itinerant idiot
Tom Tromey
2013-03-14 14:39:42 UTC
Permalink
Tom> I think it isn't possible in general. When an exception is thrown, I
Tom> think all that can really be determined is the next catch point.

Michael> This would be perfect. Explicit rethrows are relatively rare in
Michael> our code, however it is completely normal to have code that we
Michael> simply don't understand and/or follow - whereby we do:

Unfortunately, it is even worse than I made it out to be. The "next
catch point" also includes the spots where unwinding pauses to invoke
destructors. So, at the lowest level you can't easily associate a
"throw" with a "catch" that you would see in the source. You can only
see to spot that calls the next destructor.

If you're interested you can see this in action by breaking at the
"libgcc:unwind" probe point (or equivalently _Unwind_DebugHook but then
you have to do argument decoding by hand), then "disassem $_probe_arg1".
That will show you the assembly for where you're about to unwind to.

("catch catch" and "catch throw" hook into somewhat higher-level
exception functions in libstdc++, which is why they don't stop at
destructors; but this code doesn't know about the details and just
defers to the lower-level unwinder.)

I thought a bit about whether we could fix the lower levels to expose
the information we'd like, but I couldn't think of a good way.

Tom> catch catch [REGEXP]
Tom> catch throw [REGEXP]
Tom> catch rethrow [REGEXP]

Michael> This is really nice; the ability to hide many of the "two
Michael> dozen+" expected exceptions in some way be really
Michael> useful. Unfortunately these are often of quite generic types
Michael> :-)

Yeah, that makes it harder.

If they carry any identifying markers, you can use two of the features
in tandem to filter. For example:

catch throw TheExceptionType if $_exception.field == 23

This would filter by type and then do some additional checking of the
identity of the exception.

If they don't carry identifying markers -- if it were me, I guess I
would add something to make debugging simpler.

Michael> Tor suggested on IRC some way of ignoring specific exception throwing
Michael> sites which tend to creep into the code over time and need tackling.

Michael> Being able to say: catch throw ignore - which would ignore the last
Michael> thrown exception site would be really lovely ;-)

This sort of thing is reasonably easy to do in Python.

Fedora has had a $_caller_is function in its gdb for a while (not sure
why we haven't upstreamed this yet, probably just nobody got around to
it yet). This function was actually my motivation for getting into gdb
hacking and working on Python in gdb -- I wanted to be able to filter
breakpoints according to caller, without a lot of hassle.

I've appended it for convenience. I'll file a bug to remind us to
upstream this.

You would use it like:

catch throw if !$_caller_is('some_thrower_to_ignore')

You could wrap this up in a Python command to add "&& !$_caller_is..."
to existing breakpoint conditions, or a LibreOffice-specific "catch
throw"-like command which pre-ignores uninteresting call sites, or etc.

Michael> Is any of that useful ? - really looking forward to getting a
Michael> new libstdc++ etc. with your fixes in a few months :-)

An optimist, I see :)
Actually, I assume all the patches will go in reasonably soon, but
there's a pretty long lag until the new stuff is deployed all over,
unless you're willing to build your gcc.

Please feel free to CC me on gdb questions and suggestions.
I'm very interested in your feedback.

Tom


# Caller-is functions.

# Copyright (C) 2008 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/>.

import gdb
import re

class CallerIs (gdb.Function):
"""Return True if the calling function's name is equal to a string.
This function takes one or two arguments.
The first argument is the name of a function; if the calling function's
name is equal to this argument, this function returns True.
The optional second argument tells this function how many stack frames
to traverse to find the calling function. The default is 1."""

def __init__ (self):
super (CallerIs, self).__init__ ("caller_is")

def invoke (self, name, nframes = 1):
frame = gdb.selected_frame ()
while nframes > 0:
frame = frame.older ()
nframes = nframes - 1
return frame.name () == name.string ()

class CallerMatches (gdb.Function):
"""Return True if the calling function's name matches a string.
This function takes one or two arguments.
The first argument is a regular expression; if the calling function's
name is matched by this argument, this function returns True.
The optional second argument tells this function how many stack frames
to traverse to find the calling function. The default is 1."""

def __init__ (self):
super (CallerMatches, self).__init__ ("caller_matches")

def invoke (self, name, nframes = 1):
frame = gdb.selected_frame ()
while nframes > 0:
frame = frame.older ()
nframes = nframes - 1
return re.match (name.string (), frame.name ()) is not None

CallerIs()
CallerMatches()
Tom Tromey
2013-02-27 21:28:54 UTC
Permalink
Tom> The various low-level exception-related functions, like __cxa_throw,
Tom> treat the exception object as a "void *". However, the value of this
Tom> seems to change depending on the "throw" point. It's clear that this
Tom> can't always be the argument to throw, due to scalar and object throws.
Tom> So I wonder what exactly it refers to. I'll have to dig a bit deeper to
Tom> see how all this code really works.
[...]
Tom> It seems like it would be nice if gdb exposed some kind of convenience
Tom> variable so that "catch catch" and "catch throw" could be conditional on
Tom> the thrown object without needing the libstdc++ debuginfo.
[...]
Tom> This may require some libstdc++ change, perhaps a probe point.

I did some more digging here and wrote a few patches.

When throwing an exception, the compiler arranges to allocate an
internal exception object with enough extra space for the exception
passed to "throw". Then it copy-constructs from the thrown object into
this space and it records the object's type_info into the internal
exception object.

I added some SDT probes to libstdc++ to expose this information more
nicely (there's really no good way to do it in all cases right now, even
with debuginfo installed, as a couple of the probes are mid-function).

So now I can:

(gdb) catch throw
Catchpoint 1 (throw)
(gdb) r
Starting program: /home/tromey/Space/SecondArcher/build/gdb/testsuite/gdb.cp/exception
[... loads of gunk ...]
Catchpoint 1 (exception thrown), __cxxabiv1::__cxa_throw (obj=0x601090,
tinfo=0x600e60 <typeinfo for int@@CXXABI_1.3>, dest=0x0)
at ../../../../gcc/libstdc++-v3/libsupc++/eh_throw.cc:63
63 PROBE2 (throw, obj, tinfo);
(gdb) p $_exception
$1 = 13

Note that the exception variable automatically has the right type:

(gdb) up
#1 0x0000000000400873 in foo (i=20)
at ../../../archer/gdb/testsuite/gdb.cp/exception.cc:28
28 throw (int) 13;

So, cool.

The bad news is, since this requires a libstdc++ patch, even once I get
everything tidied up and approved and committed, it is going to be a
while before you can use it, unless you're willing to build your own gcc
and gdb.

Tom> If we had the convenience variable mentioned above, and if LibreOffice
Tom> has a relatively simple "exception identity" measure (e.g., if you only
Tom> throw pointers, you can just compare them with ==), then it could
Tom> perhaps be done by: break at the losing catch, make a conditional "catch
Tom> throw", then re-run.

This does turn out to be a tricky bit.
gdb generally mimics the source language, so things like:

cond 5 $_exception == 23

... will fail if some exception thrown is not actually comparable to 23.

I'm investigating some options here. Maybe a more-magical "==="
operator, or maybe a Python convenience function like:

cond 5 $_dwim_equals ($_exception, 23)


I also implemented a way to filter exception catches by name:

catch catch [REGEXP]
catch throw [REGEXP]
catch rethrow [REGEXP]

That will help the above problem a bit, you can do:

catch catch int if $_exception == 23


I think all this should help with the problems that started this thread.

Insight, advice, ideas -- send them my way.

thanks,
Tom
Noel Grandin
2013-06-24 08:37:52 UTC
Permalink
Hi

In the vein of asking for GDB improvements, I'm chasing exceptions again:

I ran LO using:
make debugrun
and then once LO is running, I stopped it using CTRL-C and did:
br
com::sun::star::lang::IllegalArgumentException::IllegalArgumentException
which ended up taking approx 10 mins to complete (on a pretty beefy machine)

Is this normal?

This is on a 64-bit fedora-18 box, gdb says:
GNU gdb (GDB) Fedora (7.5.1-38.fc18)


Thanks, Noel Grandin

Disclaimer: http://www.peralex.com/disclaimer.html

Stephan Bergmann
2013-05-30 07:28:46 UTC
Permalink
Post by Tom Tromey
FWIW we have the same problem in reverse: the gdb group at Red Hat is,
among other things, tasked with improving the C++ debugging experience.
However, most of us don't actually debug C++ programs on a regular
basis. We do know some issues, via bugzilla and other discussions, but
I feel sure we are also missing things.
One related thing that struck me now is how
__gnu_cxx::__verbose_terminate_handler prints out a helpful message to
stderr, but if all you have is a gdb backtrace upon the resulting
SIGABRT (like in thread 1 of
<https://bugzilla.redhat.com/attachment.cgi?id=754479> attached to
<https://bugzilla.redhat.com/show_bug.cgi?id=968424> ABRT report), you
cannot see what that message was.

It would be cool if there were a way to see that message in the gdb
backtrace. Like __gnu_cxx::__verbose_terminate_handler assembling the
message and then calling a not-optimized-away helper function with the
message as argument, which in turn calls fputs and abort (though I
notice that __verbose_terminate_handler currently "assembles" messages
through multiple calls to fputs, which saves it from malloc hassles).

Stephan
Tom Tromey
2013-05-30 20:24:37 UTC
Permalink
Stephan> It would be cool if there were a way to see that message in the gdb
Stephan> backtrace. Like __gnu_cxx::__verbose_terminate_handler assembling the
Stephan> message and then calling a not-optimized-away helper function with the
Stephan> message as argument, which in turn calls fputs and abort (though I
Stephan> notice that __verbose_terminate_handler currently "assembles" messages
Stephan> through multiple calls to fputs, which saves it from malloc hassles).

I think it may be fixable in GCC. Right now the issue is that 't'
is optimized away; but in a local build I can see:

(gdb) info local
terminating = true
t = 0x601060 <_ZTIi@@CXXABI_1.3>

... which, while not completely readable, is at least transformable with
c++filt or "maint demangle".

I pinged a local GCC hacker, I'll let you know if he thinks this is
fixable.

Tom
Tom Tromey
2013-05-31 19:33:43 UTC
Permalink
Tom> I think it may be fixable in GCC.

I filed a GCC bug:

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57487

You can CC yourself on it if you want to see what happens.

Tom
Stephan Bergmann
2013-06-03 06:42:35 UTC
Permalink
Post by Tom Tromey
Tom> I think it may be fixable in GCC.
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57487
You can CC yourself on it if you want to see what happens.
Great, thanks. (So your idea would be to, in a second step, teach ABRT
to issue additional gdb commands besides "backtrace" in case the trace
contains __gnu_cxx::__verbose_terminate_handler, right?)

Stephan
Tom Tromey
2013-06-03 14:36:20 UTC
Permalink
Stephan> Great, thanks. (So your idea would be to, in a second step, teach
Stephan> ABRT to issue additional gdb commands besides "backtrace" in case the
Stephan> trace contains __gnu_cxx::__verbose_terminate_handler, right?)

If that GCC bug is fixed, then the existing "bt full" would capture the
type information. The info would appear in a mangled form, but it is
trivial to run that through c++filt.

Like in my example:

t = 0x601060 <_ZTIi@@CXXABI_1.3>

->

$ c++filt _ZTIi
typeinfo for int

Tom
Stephan Bergmann
2013-06-03 14:50:38 UTC
Permalink
Post by Tom Tromey
Stephan> Great, thanks. (So your idea would be to, in a second step, teach
Stephan> ABRT to issue additional gdb commands besides "backtrace" in case the
Stephan> trace contains __gnu_cxx::__verbose_terminate_handler, right?)
If that GCC bug is fixed, then the existing "bt full" would capture the
type information. The info would appear in a mangled form, but it is
trivial to run that through c++filt.
Ah, right; I'd failed to note how "bt full" also prints local vars, and
how e.g. ABRT already uses that.

Stephan
Loading...