According to the documentation I can break on specific exception type by using conditional breakpoints. However the syntax for the condition isn\'t very clear to me:
Let's assume you have the following code.cpp with a thread that throws an exception:
#include <iostream>
#include <thread>
void thr()
{
while (true) {
new int[1000000000000ul];
}
}
int main(int argc, char* argv[]) {
std::thread t(thr);
t.join();
std::cout << "Hello, World!" << std::endl;
return 0;
}
Compile it with using the following CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(tutorial)
set(CMAKE_CXX_STANDARD 11)
add_executable(test_exceptions main.cpp)
target_link_libraries(test stdc++ pthread)
Now you can play with, running it will give you an abort because of bad_alloc.
Before going on, it's better if you install libstd debug symbols, sudo apt-get install libstdc++6-5-dbg
or whatever version you have.
If you are compiling in Debug
you can follow this answer https://stackoverflow.com/a/12434170/5639395 because constructors are usually defined.
If you are compiling in DebWithRelInfo
you may not be able to find a proper constructor where to put your breakpoint because of the compiler optimization. In this case, you have some other options. Let's continue.
If you can change the source code easily, this will work https://stackoverflow.com/a/9363680/5639395
If you don't want to change the code, you can try to see if catch throw bad_alloc
or in general catch throw exception_name
works.
I will build on top of this answer https://stackoverflow.com/a/6849989/5639395
We will add a breakpoint in gdb in the function __cxxabiv1::__cxa_throw
. This function takes a parameter called tinfo
that has the information we need to conditionally check for the exception we care about.
We want something like catch throw if exception==bad_alloc, so how to find the proper comparison?
It turns out that tinfo
is a pointer to a structure that has a variable called __name
inside. This variable has a string with the mangled name of the exception type.
So we can do something like: catch throw if tinfo->__name == mangled_exception_name
We are almost there!
We need a way to do string comparison, and it turns out gdb has a built-in function $_streq(str1,str2) that does exactly what we need. The mangled name of the exception is a little harder to find, but you can try to guess it or check the Appendix of this answer. Let's assume for now it is "St9bad_alloc".
The final instruction is:
catch throw if $_streq(tinfo->__name , "St9bad_alloc")
or equivalent
break __cxxabiv1::__cxa_throw if $_streq(tinfo->__name , "St9bad_alloc")
You have two options
Assuming that you installed the libstd debug symbols, you can find the library name like this:
apt search libstd | grep dbg | grep installed
The name is something like this libstdc++6-5-dbg
Now check the files installed:
dpkg -L libstdc++6-5-dbg
Look for something that has a debug in the path, and a .so extension. In my pc I have /usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.21
.
Finally, look for the exception you want in there.
nm /usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.21 | grep -i bad_alloc
Or
nm /usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.21 | grep -i runtime_error
etc.
In my case I found something like 00000000003a4b20 V _ZTISt9bad_alloc
which suggested me to use "St9bad_alloc" as the name.
This is easy, just start gdb, catch throw
everything and run the small executable I wrote before. When you are inside gdb, you can issue a p *tinfo
and look for the __name
description from gdb.
gdb -ex 'file test_exceptions' -ex 'catch throw' -ex 'run'
(gdb) p *tinfo
$1 = {_vptr.type_info = 0x406260 <vtable for __cxxabiv1::__si_class_type_info+16>,
__name = 0x7ffff7b8ae78 <typeinfo name for std::bad_alloc> "St9bad_alloc"}
I think I can answer the part about setting conditional breaks. I won't answer question regarding exceptions as __raise_exception seems to not exist in g++ 4.5.2 (?)
Let's assume that you have following code (I use void to get something similar to __raise_exception from gdb doc)
void foo(void* x) {
}
int main() {
foo((void*)1);
foo((void*)2);
}
to break at foo(2) you use following commands
(gdb) break foo
Breakpoint 1 at 0x804851c: file q.cpp, line 20.
(gdb) condition 1 x == 2
If you run with
(gdb) r
you will see that it stops on the second foo call, but not on the first one
I think, what they meant in docs is that you set break on function __raise_exception (very implementation dependent)
/* addr is where the exception identifier is stored
id is the exception identifier. */
void __raise_exception (void **addr, void *id);
and then set conditional break on id as described above (you have to somehow determine what is id for yours exception type).
Unfortunately
(gdb) break __raise_exception
results with (g++ 4.5.2)
Function "__raise_exception" not defined.
When gdb command 'catch throw' fails, try this workaround :
(tested with Linux g++ 4.4.5/gdb 6.6)
1/ Add this code anywhere in the program to debug :
#include <stdexcept>
#include <exception>
#include <typeinfo>
struct __cxa_exception {
std::type_info *inf;
};
struct __cxa_eh_globals {
__cxa_exception *exc;
};
extern "C" __cxa_eh_globals* __cxa_get_globals();
const char* what_exc() {
__cxa_eh_globals* eh = __cxa_get_globals();
if (eh && eh->exc && eh->exc->inf)
return eh->exc->inf->name();
return NULL;
}
2/ In gdb you will then be able to filter exceptions with :
(gdb) break __cxa_begin_catch
(gdb) cond N (what_exc()?strstr(what_exc(),"exception_name"):0!=0)
where N is the breakpoint number, and exception_name is the name of exception for which we wish to break.
I'm not sure if this is a recent fix, but with GDB GNU gdb (Debian 9.1-2) 9.1
, I have used catch throw std::logical_error
successfully. I would hate to generalise prematurely, but it is possible this now works correctly in GDB (April 2020).
EDIT
The documentation suggests that catch throw <exceptname>
can be used to break whenever an exception of type <exceptname>
is thrown; however, that doesn't seem to work in practice.
(gdb) help catch
Set catchpoints to catch events.
Raised signals may be caught:
catch signal - all signals
catch signal <signame> - a particular signal
Raised exceptions may be caught:
catch throw - all exceptions, when thrown
catch throw <exceptname> - a particular exception, when thrown
catch catch - all exceptions, when caught
catch catch <exceptname> - a particular exception, when caught
Thread or process events may be caught:
catch thread_start - any threads, just after creation
catch thread_exit - any threads, just before expiration
catch thread_join - any threads, just after joins
Process events may be caught:
catch start - any processes, just after creation
catch exit - any processes, just before expiration
catch fork - calls to fork()
catch vfork - calls to vfork()
catch exec - calls to exec()
Dynamically-linked library events may be caught:
catch load - loads of any library
catch load <libname> - loads of a particular library
catch unload - unloads of any library
catch unload <libname> - unloads of a particular library
The act of your program's execution stopping may also be caught:
catch stop
C++ exceptions may be caught:
catch throw - all exceptions, when thrown
catch catch - all exceptions, when caught
Ada exceptions may be caught:
catch exception - all exceptions, when raised
catch exception <name> - a particular exception, when raised
catch exception unhandled - all unhandled exceptions, when raised
catch assert - all failed assertions, when raised
Do "help set follow-fork-mode" for info on debugging your program
after a fork or vfork is caught.
Do "help breakpoints" for info on other commands dealing with breakpoints.
In case the problem is that there is no valid stack trace (not breaking in raise), it seems to be a problem when re-compiling without re-starting gdb. ( i.e. calling "make" inside the gdb console).
After having re-started gdb, it breaks correctly in raise.c (my versions : GNU gdb 8.1.0.20180409-git, gcc 7.4.0, GNU make 4.1)